code
Clone this repository
https://tangled.org/bretton.dev/coves
git@knot.bretton.dev:bretton.dev/coves
For self-hosted knots, clone URLs may differ based on your setup.
Add comprehensive documentation explaining why vote write-forward
endpoints were removed:
- OAuth DPoP tokens are cryptographically bound to client's private key
- Backend cannot create DPoP proofs (doesn't have the key)
- Solution: Clients write directly to their PDS using
com.atproto.repo.createRecord/deleteRecord
- AppView indexes votes from Jetstream for aggregation
Also documented future work needed for subscriptions and blocking.
See: docs/PRD_BACKLOG.md#oauth-dpop-token-architecture
- Document P0 blocker: DPoP token write-forward architecture issue
- Analyze correct client-direct-write pattern (following Bluesky)
- Add feed system implementation documentation
- Clean up CLAUDE.md whitespace
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- CovesClient.create_post() accepts optional title parameter
- Kagi aggregator now passes story titles to posts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add mobile testing targets to Makefile (adb port forwarding, ngrok)
- Fix PDS port configuration (3000→3001 for correct DID registration)
- Add AUTH_SKIP_VERIFY flag for local JWT development
- Add scripts for mobile port setup and ngrok tunnels
- Add bin/ to .gitignore for build artifacts
Enables USB-connected Android testing and iOS/WiFi testing via ngrok
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements a production-ready voting system following atProto write-forward
architecture with bidirectional voting (upvote/downvote) for forum-style
content ranking.
## Key Features
- **atProto Write-Forward Architecture**: AppView → PDS → Jetstream → AppView
- **User-Owned Votes**: Votes stored in user repositories (at://user_did/...)
- **Strong References**: URI + CID for content integrity
- **Toggle Logic**: Same direction deletes, opposite direction switches
- **Real-time Indexing**: Jetstream consumer with atomic count updates
- **PDS-as-Source-of-Truth**: Queries PDS directly to prevent race conditions
## Components Added
### Domain Layer (internal/core/votes/)
- Vote model with strong reference support
- Service layer with PDS integration and toggle logic
- Repository interface for data access
- Domain errors (ErrVoteNotFound, ErrSubjectNotFound, etc.)
- Comprehensive service unit tests (5 tests, all passing)
### Data Layer (internal/db/postgres/)
- Vote repository implementation with idempotency
- Comprehensive unit tests (11 tests covering all CRUD + edge cases)
- Migration #013: Create votes table with indexes and constraints
- Migration #014: Remove FK constraint (critical race condition fix)
### API Layer (internal/api/)
- CreateVoteHandler: POST /xrpc/social.coves.interaction.createVote
- DeleteVoteHandler: POST /xrpc/social.coves.interaction.deleteVote
- Shared error handler (handlers/errors.go) for consistency
- OAuth authentication required on all endpoints
### Jetstream Integration (internal/atproto/jetstream/)
- VoteEventConsumer: Indexes votes from firehose
- Atomic transaction: vote insert + post count update
- Security validation: DID format, direction, strong references
- Idempotent operations for firehose replays
### Testing (tests/integration/)
- E2E test with simulated Jetstream (5 scenarios, <100ms)
- TRUE E2E test with live PDS + Jetstream (1.3s, all passing)
- Verified complete data flow: API → PDS → Jetstream → AppView
## Critical Fixes
### Fix #1: Toggle Race Condition
**Problem**: Querying AppView (eventually consistent) caused duplicate votes
**Solution**: Query PDS directly via com.atproto.repo.listRecords
**Impact**: Eliminates data corruption, adds ~75ms latency (acceptable)
### Fix #2: Voter Validation Race
**Problem**: Vote events arriving before user events caused permanent vote loss
**Solution**: Removed FK constraint, allow out-of-order indexing
**Migration**: 014_remove_votes_voter_fk.sql
**Security**: Maintained via PDS authentication + DID format validation
### Fix #3: PDS Pagination
**Problem**: Users with >100 votes couldn't toggle/delete votes
**Solution**: Full pagination with reverse=true (newest first)
**Capacity**: Supports up to 5000 votes per user (50 pages × 100)
## Technical Implementation
**Lexicon**: social.coves.interaction.vote (record type)
- subject: StrongRef (URI + CID)
- direction: "up" | "down"
- createdAt: datetime
**Database Schema**:
- Unique constraint: one active vote per user per subject
- Soft delete support (deleted_at)
- DID format constraint (removed FK for race condition fix)
- Indexes: subject_uri, voter_did+subject_uri, voter_did
**Service Logic**:
- Validates subject exists before creating vote
- Queries PDS for existing vote (source of truth)
- Implements toggle: same → delete, different → switch
- Writes to user's PDS with strong reference
**Consumer Logic**:
- Listens for social.coves.interaction.vote CREATE/DELETE
- Validates: DID format, direction, strong reference
- Atomically: indexes vote + updates post counts
- Idempotent: ON CONFLICT DO NOTHING, safe for replays
## Test Coverage
✅ Repository Tests: 11/11 passing
✅ Service Tests: 5/5 passing (1 skipped by design)
✅ E2E Simulated: 5/5 passing
✅ E2E Live PDS: 1/1 passing (TRUE end-to-end)
✅ Build: Success
**Total**: 22 tests, ~3 seconds
## Architecture Compliance
✅ Write-forward pattern (AppView → PDS → Jetstream → AppView)
✅ Layer separation (Handler → Service → Repository → Database)
✅ Strong references for content integrity
✅ Eventual consistency with out-of-order event handling
✅ Idempotent operations for distributed systems
✅ OAuth authentication on all write endpoints
## Performance
- Vote creation: <100ms (includes PDS write)
- Toggle operation: ~150ms (includes PDS query + write)
- Jetstream indexing: <1 second (real-time)
- Database indexes: Optimized for common query patterns
## Security
✅ JWT authentication required
✅ Votes validated against user's PDS repository
✅ DID format validation
✅ Strong reference integrity (URI + CID)
✅ Rate limiting (100 req/min per IP)
🚀 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## 🎉 Major Feature: Complete Feed System Implementation
Implements the complete Timeline and Discover feed architecture for Coves,
following atProto patterns with proper separation of concerns (handlers →
services → repositories). This is a foundational feature for the alpha release.
### New Features
**Timeline Feed (User-Specific)**
- `social.coves.feed.getTimeline` XRPC endpoint
- Authenticated user feed showing posts from subscribed communities
- Full architecture: Handler → Service → Repository
- Integration tests with cursor pagination (368 lines)
**Discover Feed (Public)**
- `social.coves.feed.getDiscover` XRPC endpoint
- Public feed showing recent posts from all communities
- Optimized for performance with documented indexing strategy
- Comprehensive security tests (273 lines)
**Shared Infrastructure**
- Created `feed_repo_base.go` (340 lines) to eliminate code duplication
- Shared lexicon definitions in `social.coves.feed.defs`
- HMAC-SHA256 signed cursors for pagination integrity
- Consistent error handling across both feeds
### Technical Improvements
**Code Quality**
- Eliminated ~700 lines of duplicate code via shared base repository
* timeline_repo.go: 426 → 131 lines (-69% duplication)
* discover_repo.go: 383 → 124 lines (-68% duplication)
- Consistent formatting with gofumpt
- Comprehensive inline documentation
**Security Enhancements**
- HMAC cursor signing prevents pagination tampering
- CURSOR_SECRET environment variable for production deployments
- DID format validation (must start with "did:")
- Rate limiting strategy documented (100 req/min per IP)
- Input validation at handler level
**Performance**
- Database indexes documented for optimal query performance
- Cursor-based pagination for large result sets
- Efficient joins between posts, communities, and users
### Aggregator System Updates
**Documentation**
- Documented critical alpha blocker: aggregator user registration
- Aggregators cannot post until indexed as users in AppView
- Proposed solution: `social.coves.aggregator.register` endpoint
- Quick fix alternative documented for testing
**Code Cleanup**
- Consistent formatting across aggregator codebase
- Improved test readability
- Updated PRD with alpha blockers section
### Files Changed (30 files, +2406/-308 lines)
**New Implementations**
- `internal/api/handlers/timeline/` - Timeline XRPC handler
- `internal/api/handlers/discover/` - Discover XRPC handler
- `internal/core/timeline/` - Timeline business logic
- `internal/core/discover/` - Discover business logic
- `internal/db/postgres/feed_repo_base.go` - Shared repository base
- `internal/db/postgres/timeline_repo.go` - Timeline data access
- `internal/db/postgres/discover_repo.go` - Discover data access
- `internal/atproto/lexicon/social/coves/feed/` - Feed lexicons
- `tests/integration/timeline_test.go` - Timeline integration tests
- `tests/integration/discover_test.go` - Discover integration tests
**Updated**
- `cmd/server/main.go` - Feed service initialization
- `internal/api/routes/` - Timeline and discover routes
- `docs/aggregators/PRD_AGGREGATORS.md` - Alpha blocker docs
- `tests/integration/helpers.go` - Shared test utilities
### Testing
- ✅ All 11 integration tests passing
- ✅ Timeline feed with authentication
- ✅ Discover feed security validation
- ✅ Cursor pagination integrity
- ✅ Aggregator authorization flows
### Migration Path
To revert this entire feature in the future:
```bash
git revert -m 1 <this-merge-commit-id>
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add critical alpha blocker documentation for aggregator registration system.
Aggregators cannot post until they are indexed as users in the AppView.
Changes:
- Document aggregator user registration blocker in PRD
- Add proposed solution (registration endpoint)
- Include quick fix alternative for testing
- Code formatting cleanup across aggregator files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>