feat(voting): implement complete voting system with atProto integration
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>