commits
Added infrastructure/technical TODOs:
- OAuth authentication for community actions (P1)
- Jetstream consumer race condition (P2)
- Structured logging migration (P3)
- PDS URL resolution from DID (P3)
- PLC directory registration for prod (P3)
Feature-specific TODOs (avatars, moderator checks, update/delete handlers)
are tracked in their respective PRDs (PRD_COMMUNITIES, PRD_GOVERNANCE).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem:
- Default INSTANCE_DID was did:web:coves.local
- instanceDomain extracted as "coves.local"
- Community handles generated as "{name}.communities.coves.local"
- .local TLD is disallowed per atProto spec (RFC 6762)
- Result: Community creation failed immediately with InvalidHandleError
Solution:
- Changed default to did:web:coves.social (.social is valid TLD)
- Added TODO comment documenting did:web domain verification security issue
- Created docs/PRD_BACKLOG.md to track follow-up work
Security Note:
Self-hosters can currently set INSTANCE_DID to any domain without
verification. This enables domain impersonation attacks. Added to
backlog (P0) to implement did:web verification per atProto spec.
Testing:
- All integration tests pass (TestCommunity_E2E)
- Community handles now: gaming.communities.coves.social ✓
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add logging for internal server errors in community XRPC handlers to
aid debugging during development. Internal errors are now logged before
returning generic error responses to clients.
Changes:
- Add log import to errors.go
- Log actual error details when returning InternalServerError
- Add TODO comment to migrate to proper structured logger
Rationale:
During E2E testing, internal errors were being silently swallowed making
debugging difficult. This change logs the actual error while still
returning safe generic error messages to clients.
Security note:
- Internal error details are NOT exposed to clients
- Logging is for server-side debugging only
- Generic "InternalServerError" message still returned to clients
TODO: Replace log.Printf with structured logger (e.g., zerolog/zap)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update development PostgreSQL port from 5433 to 5435 to avoid conflicts
with existing local PostgreSQL installations or other services.
Changes:
- .env.dev: Update POSTGRES_PORT to 5435
- docker-compose.dev.yml: Update health checks to use wget instead of curl
- cmd/server/main.go: Update default DATABASE_URL to use port 5435
Additional improvements:
- Replace curl with wget in Docker healthchecks (more reliable in Alpine)
- Update comments to reflect new port configuration
Ports summary:
- Dev PostgreSQL: 5435 (was 5433)
- Test PostgreSQL: 5434 (unchanged)
- PDS: 3001
- AppView: 8081
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive technical decisions to PRDs documenting architecture
choices for community handles and moderator record storage.
PRD_COMMUNITIES.md:
- Add technical decision: Single handle field (2025-10-11)
- Update lexicon summary to reflect DNS-valid handle approach
- Add DNS infrastructure checklist items (wildcard setup, well-known endpoint)
- Document that !name@instance format is client-side display only
PRD_GOVERNANCE.md:
- Add technical decision: Moderator records storage location (2025-10-11)
- Document security analysis comparing user repo vs community repo
- Explain attack vector for malicious self-hosted instances
- Rationale: Community repo provides better security and federation
Key decisions documented:
1. Single handle field matches Bluesky pattern (app.bsky.actor.profile)
2. Separation of concerns: protocol (DNS handle) vs presentation (!prefix)
3. Moderator records in community repo prevents forgery attacks
4. DNS wildcard required for *.communities.coves.social resolution
Infrastructure requirements added:
- [ ] DNS Wildcard Setup: Configure *.communities.coves.social
- [ ] Well-Known Endpoint: Implement .well-known/atproto-did handler
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update all community tests to use DNS-valid atProto handles instead of
scoped handle format. All tests passing including E2E, integration, and
unit test suites.
Changes:
- Update test fixtures to use DNS-valid handles
- Remove atprotoHandle references from test data
- Rename TestCommunityConsumer_AtprotoHandleField to TestCommunityConsumer_HandleField
- Update test assertions to expect DNS format handles
- Fix unused variable warnings in unit tests
Test coverage:
✅ E2E tests (5.57s) - Full PDS → Jetstream → AppView flow
✅ Integration tests (4.36s) - 13 suites covering CRUD, credentials, V2 validation
✅ Unit tests (0.37s) - Service layer, timeout handling, credentials
✅ Lexicon validation (0.40s) - All 60 schemas validated
Example test data changes:
- Before: handle="!gaming@coves.social"
- After: handle="gaming.communities.coves.social"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update community service and consumer to work with single handle field.
Remove scoped handle generation (!name@instance) and store DNS-valid
atProto handle directly.
Changes:
- Remove scoped handle generation logic
- Update handle validation regex to accept DNS format
- Store pdsAccount.Handle directly (e.g., gaming.communities.coves.social)
- Consumer uses handle field directly from profile record
- Update comments to reflect single handle approach
Technical details:
- Regex now validates standard DNS hostname format (RFC 1035)
- Allows subdomain format: name.communities.instance.com
- Client UI will derive !name@instance display from name + instance
Impact:
- All E2E tests passing with real PDS and Jetstream
- Handle resolution works correctly
- Community creation/update flows validated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove redundant atprotoHandle field in favor of single DNS-resolvable
handle field. This matches Bluesky's pattern (app.bsky.actor.profile)
and follows atProto best practices.
Changes:
- Remove atprotoHandle field from social.coves.community.profile
- Update handle field description to indicate DNS-resolvable format
- Add format: "handle" validation
- Update test data to use DNS-valid handles
Rationale:
- Single source of truth for community handle
- Reduces confusion about which handle is "real"
- Client-side UI derives display format (!name@instance) from name + instance
- Follows separation of concerns: protocol vs presentation layer
Example:
- Before: handle="!gaming@coves.social", atprotoHandle="gaming.communities.coves.social"
- After: handle="gaming.communities.coves.social", display derived client-side
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated PDS_SERVICE_HANDLE_DOMAINS in .env.dev to include both:
- .local.coves.dev (for user accounts)
- .communities.coves.social (for V2 community accounts)
This allows E2E tests to successfully create community PDS accounts with
the proper subdomain. Required for V2 Communities architecture where each
community owns its own PDS account and repository.
Also added PRD_GOVERNANCE.md documenting governance mechanisms.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Minor documentation updates to main.go initialization code
to reflect V2 architecture and current implementation status.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**PROBLEM**: Local PostgreSQL system service running on port 5433,
preventing dev docker-compose stack from starting.
**FIX**: Changed coves-dev-postgres port mapping from 5433→5432 to 5435→5432
**ENVIRONMENT PORTS**:
- Local PostgreSQL system: 5432, 5433
- Test PostgreSQL (docker): 5434
- Dev PostgreSQL (docker): 5435 (NEW)
- PDS (docker): 3001
**ALSO ADDED**:
- PDS_INVITE_REQUIRED: "false" (disable invite codes for testing)
- Updated PDS_SERVICE_HANDLE_DOMAINS to include .communities.coves.social
(allows community handles like community-name.communities.coves.social)
**IMPACT**:
- Dev stack can now start without port conflicts
- E2E tests can create community PDS accounts
- No changes required to test database setup
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**COMPREHENSIVE UPDATE**:
Restructured PRD_COMMUNITIES.md to focus on:
1. ✅ What's been implemented and tested
2. ⏳ What's in progress
3. 📋 What's remaining before V1 launch
**COMPLETED WORK** (documented):
- V2 Architecture (communities own PDS accounts)
- Credential management (persistence + encryption)
- Jetstream consumer (real-time firehose indexing)
- Repository layer (PostgreSQL with atomic operations)
- XRPC endpoints (create, get, update, list)
- Comprehensive test coverage
**CRITICAL FIXES** (documented):
- P0: PDS credential persistence
- P0: UpdateCommunity authentication
- V2 enforcement (removed V1 compatibility)
- Encryption at rest
- Dynamic timeouts
**ROADMAP** (documented):
- OAuth flows (in progress)
- Rate limiting and visibility enforcement
- Posts in communities
- Moderation tools
- Federation improvements
**CHANGES**:
- Removed code examples (kept PRD focused on status)
- Added "Recent Critical Fixes" section
- Organized by implementation status
- Clear V1 launch checklist
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**NEW TESTS**:
1. **community_credentials_test.go** - Integration tests:
- TestCommunityRepository_CredentialPersistence
* Verify PDS credentials saved to database
* Verify credentials retrievable after creation
- TestCommunityRepository_EncryptedCredentials
* Verify credentials encrypted in database (not plaintext)
* Verify decryption works correctly on retrieval
- TestCommunityRepository_V2OwnershipModel
* Verify owner_did == did (self-owned)
2. **community_v2_validation_test.go** - Integration tests:
- TestCommunityConsumer_V2RKeyValidation
* Accept rkey="self" (V2 communities)
* Reject rkey with TID pattern (V1 communities)
* Reject custom rkeys
- TestCommunityConsumer_AtprotoHandleField
* Verify handle field handling in consumer
3. **community_service_test.go** - Unit tests:
- TestCommunityService_PDSTimeouts
* Verify write ops use 30s timeout
* Verify read ops use 10s timeout
- TestCommunityService_UpdateWithCredentials
* Verify UpdateCommunity fetches credentials from DB
* Verify error if credentials missing
- TestCommunityService_CredentialPersistence
* Verify repo.Create() called with credentials
4. **community_e2e_test.go** - Enhanced E2E tests:
- Fixed .local TLD issue (changed to .social)
- Fixed handle length issue (use shorter test names)
- Complete flow: Service → PDS → Jetstream → Consumer → DB → XRPC
**TEST COVERAGE**:
- ✅ P0 credential persistence bug
- ✅ P0 UpdateCommunity authentication bug
- ✅ Encryption at rest
- ✅ V2 rkey validation
- ✅ Dynamic timeout logic
- ✅ End-to-end write-forward flow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**SECURITY ENHANCEMENT**: Encrypt community PDS access/refresh tokens
in PostgreSQL using pgcrypto extension.
**IMPLEMENTATION**:
1. **Migration 006**:
- Enable pgcrypto extension
- Create encryption_keys table with single 256-bit key
- Add encrypted BYTEA columns: pds_access_token_encrypted, pds_refresh_token_encrypted
- Generate random encryption key on first run (idempotent)
- Add index for communities with credentials
2. **Repository Layer**:
- Encrypt on INSERT using pgp_sym_encrypt()
- Decrypt on SELECT using pgp_sym_decrypt()
- Inline encryption/decryption (no application-layer crypto)
- Empty strings stored as NULL (skip encryption)
**KEY MANAGEMENT**:
- Single symmetric key stored in encryption_keys table
- Key persists across restarts via PostgreSQL storage
- Future: Support key rotation via rotated_at timestamp
**TRADE-OFFS**:
- Performance: Inline crypto adds ~1-2ms per query
- Security: Keys stored in same DB (acceptable for self-hosted)
- Simplicity: No external KMS required for initial version
**FUTURE ENHANCEMENTS**:
- External KMS integration (AWS KMS, Vault)
- Key rotation support
- Per-community keys (if needed)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**CONTEXT**: Pre-production system should not support V1 communities.
All communities must use V2 architecture from day one.
**CHANGES**:
1. **Jetstream Consumer** - Strict V2 validation:
- REJECT any community profile with rkey != "self"
- V1 used TID-based rkeys (e.g., "3km4..."), V2 uses "self"
- Removed V1 owner field handling
- Added clear error messages for V1 detection
2. **Lexicon Schema** - Removed V1 fields:
- Removed "owner" field (V1: owner != community DID)
- V2 principle: community IS the owner (self-owned)
3. **Domain Model** - Simplified ownership:
- Removed OwnerDID field from Community struct
- V2: owner_did always equals did (enforced at creation)
**V2 ARCHITECTURE PRINCIPLES**:
- Community owns its own PDS account (did)
- Community owns its own repository (at://did/...)
- Profile always at rkey="self" (not TID-based)
- Self-owned: owner_did == did (no separate owner)
**IMPACT**:
- Cleaner codebase without V1/V2 branching logic
- Prevents accidental V1 community creation
- Enforces architectural constraints at every layer
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**PROBLEM**: PDS credentials were never saved to database, making it impossible
to update community profiles later or re-authenticate if tokens expire.
**ROOT CAUSE**: After provisioning PDS account and creating profile record,
credentials were only stored in memory (returned Community struct) but never
persisted via repository.Create().
**FIX**: Call repo.Create() immediately after PDS provisioning to persist:
- pds_access_token
- pds_refresh_token
- pds_url
- did (from PDS createAccount response)
**IMPACT**:
- Communities can now be updated using their own credentials
- Token refresh will work when access tokens expire
- Critical for V2 write-forward architecture (community updates own profile)
**ARCHITECTURE**:
This fix enables the proper V2 flow:
1. Create community → Store credentials in DB
2. Update community → Fetch credentials from DB → Authenticate as community → Write-forward to PDS
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed syntax error in 005 migration where pds_url column was missing
a trailing comma, causing migration failures.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add goose Up/Down directives to OAuth indexes migration
- Remove WHERE clause from active sessions index (PostgreSQL immutability)
- Clean up unused cookieSecret variable in OAuth test
- Ensure migration rollback works correctly
- Update bluesky-social/indigo to latest (20251003000214)
- Add CLI utility dependencies (urfave/cli, blackfriday)
- Update golang.org/x/crypto and sync libraries
- Add golang.org/x/mod for module support
Product requirements document covering:
- Feature overview and goals
- Architecture decisions (V1: instance-scoped)
- atProto federation design
- Data model and relationships
- API specifications
- Future roadmap (V2: community-owned, V3: full federation)
Test coverage:
- Repository layer: CRUD, subscriptions, search, pagination
- Consumer layer: Event handling, idempotency, filtering
- E2E: Write-forward → PDS → Firehose → Consumer → AppView → XRPC
E2E test validates:
- Full atProto write-forward architecture
- Real PDS integration (not mocked)
- Jetstream consumer indexing
- All XRPC HTTP endpoints
- Data consistency across layers
Test cleanup:
- Removed duplicate writeforward_test.go
- Removed incomplete xrpc_e2e_test.go
- Removed manual real_pds_test.go
- Kept only essential, non-overlapping tests
All tests passing ✅
- Initialize DID generator with PLC directory config
- Create Communities service with PDS connection
- Authenticate instance DID with PDS for write-forward
- Register XRPC HTTP routes
- Add graceful handling for PDS auth failures
Environment variables:
- IS_DEV_ENV: Enable dev mode (mock DID generation)
- PLC_DIRECTORY_URL: PLC directory endpoint
- PDS_URL: Personal Data Server URL
- PDS_INSTANCE_HANDLE: Instance handle for auth
- PDS_INSTANCE_PASSWORD: Instance password for auth
Endpoints implemented:
- GET /xrpc/social.coves.community.get - Retrieve by DID or handle
- GET /xrpc/social.coves.community.list - List with filters
- GET /xrpc/social.coves.community.search - Full-text search
- POST /xrpc/social.coves.community.create - Create community
- POST /xrpc/social.coves.community.subscribe - Subscribe to feed
- POST /xrpc/social.coves.community.unsubscribe - Unsubscribe
Security notes:
- TODO(Communities-OAuth): Authentication currently client-controlled
- MUST integrate OAuth middleware before production
- Authorization enforced at service layer
- Proper error mapping to HTTP status codes
Handles community events from firehose:
- Create/Update/Delete community profiles
- Subscribe/Unsubscribe events
- Uses atomic transaction methods for consistency
Key features:
- Idempotent event handling for replay safety
- Extracts community DID from record (not repo owner)
- Uses SubscribeWithCount for atomic count updates
- Proper error handling with graceful degradation
- Logs all indexed events for observability
Database schema:
- communities table with pg_trgm indexes for fuzzy search
- community_subscriptions with composite index for lookups
- community_memberships with reputation tracking
- community_moderation (V2 prepared)
Repository features:
- Atomic SubscribeWithCount/UnsubscribeWithCount transactions
- Idempotent operations for Jetstream replay safety
- Full-text search with relevance filtering (>0.2 threshold)
- Pagination and filtering support
- Proper error handling with domain error mapping
Performance optimizations:
- Composite index on (user_did, community_did) for subscription lookups
- GIN indexes for trigram similarity search
- GREATEST(0, count - 1) prevents negative counts
- Domain types: Community, Subscription, Membership
- Service layer implements atProto write-forward architecture
- Creates records on PDS (not directly in DB)
- Repository interface for AppView indexing
- Domain errors with proper categorization
- Authorization checks for community updates
Key architectural decisions:
- Communities are instance-scoped (V1)
- Records stored in instance's repo on PDS
- Community DID is metadata, not repo owner
- Service writes to PDS → Firehose → Consumer → AppView DB
- social.coves.community.create - Procedure for creating communities
- social.coves.community.profile - Community profile record schema
- social.coves.community.update - Procedure for updating communities
- Add lexicon validation test data
- Follows atProto lexicon specification for federated records
- Add CommitEvent struct for handling record commits from firehose
- Extends JetstreamEvent to support commit kind
- Required for indexing community records from Jetstream
- Part of Communities feature preparation
- Implements did:plc DID generation for community entities
- Supports dev mode (mock) and production (PLC directory registration)
- Follows atProto DID specification
- Required for Communities feature implementation
This commit implements a complete, secure OAuth 2.0 + atProto authentication system
for Coves, including comprehensive security fixes based on code review.
## 📋 Core Features
### OAuth 2.0 + atProto Authentication
- **DPoP Token Binding (RFC 9449)**: Each session has unique cryptographic key
- **PKCE (RFC 7636)**: S256 challenge method prevents code interception
- **PAR (RFC 9126)**: Pre-registration of authorization requests
- **Complete OAuth Flow**: Login → Authorize → Callback → Session Management
### Implementation Architecture
- **Handlers**: [internal/api/handlers/oauth/](internal/api/handlers/oauth/)
- `login.go` - Initiates OAuth flow with handle resolution
- `callback.go` - Processes authorization code and creates session
- `logout.go` - Session termination
- `metadata.go` - RFC 7591 client metadata endpoint
- `jwks.go` - Public key exposure (JWK Set)
- **OAuth Client**: [internal/atproto/oauth/](internal/atproto/oauth/)
- `client.go` - OAuth HTTP client with PAR, token exchange, refresh
- `dpop.go` - DPoP proof generation (ES256 signatures)
- `pkce.go` - PKCE challenge generation
- **Session Management**: [internal/core/oauth/](internal/core/oauth/)
- `session.go` - OAuth data models (OAuthRequest, OAuthSession)
- `repository.go` - PostgreSQL storage with atomic operations
- `auth_service.go` - Authentication business logic
- **Middleware**: [internal/api/middleware/auth.go](internal/api/middleware/auth.go)
- `RequireAuth` - Enforces authentication
- `OptionalAuth` - Loads user context if available
- Automatic token refresh (< 5 min to expiry)
### Database Schema
- **oauth_requests**: Temporary state during authorization flow (10-min TTL)
- **oauth_sessions**: Long-lived authenticated sessions
- **Indexes**: Performance optimizations for session queries
- **Auto-cleanup**: Trigger-based expiration handling
### DPoP Transport
- **HTTP RoundTripper**: [internal/atproto/xrpc/dpop_transport.go](internal/atproto/xrpc/dpop_transport.go)
- Automatic DPoP proof injection on all requests
- Nonce rotation handling (automatic retry on 401)
- PDS and auth server nonce tracking
## 🔐 Security Features (PR Review Hardening)
### Critical Security Fixes
✅ **CSRF/Replay Protection**: Atomic `GetAndDeleteRequest()` prevents state reuse
✅ **Cookie Secret Validation**: Enforced minimum 32 bytes for session security
✅ **Error Sanitization**: No internal error details exposed to users
✅ **HTTPS Enforcement**: Production-only HTTPS cookies with explicit localhost checks
✅ **Clean Architecture**: Business logic extracted to `AuthService` layer
### Additional Security Measures
✅ **No Token Leakage**: Never log response bodies containing credentials
✅ **Race-Free**: Fixed concurrent access to DPoP nonces with proper mutex handling
✅ **Input Validation**: Handle format checking, state parameter verification
✅ **Session Isolation**: One active session per DID (upgradeable to multiple)
✅ **Automatic Cleanup**: Hourly background job removes expired sessions/requests
### Token Binding & Proof-of-Possession
- Each session generates unique ES256 key pair
- Access tokens cryptographically bound to client
- DPoP proofs include:
- JWK header (public key)
- HTTP method and URL (prevents token replay)
- Access token hash (`ath` claim)
- JTI (unique token ID)
- Server nonce (when required)
## 🎯 Configuration & Setup
### Environment Variables
```bash
# OAuth Configuration (.env.dev)
OAUTH_PRIVATE_JWK=base64:... # Client private key (ES256)
OAUTH_COOKIE_SECRET=... # Session cookie secret (min 32 bytes)
APPVIEW_PUBLIC_URL=http://127.0.0.1:8081
```
### Base64 Encoding Support
- Helper: `GetEnvBase64OrPlain()` supports both plain and base64-encoded values
- Prevents shell escaping issues with JSON in environment variables
- Format: `OAUTH_PRIVATE_JWK=base64:eyJhbGci...` or plain JSON
### Cookie Store Singleton
- Global singleton initialized at startup: `oauth.InitCookieStore(secret)`
- Shared across all handlers for consistent session management
- Validates secret length on initialization
### Database Migration
```sql
-- Migration 003: OAuth tables
-- Migration 004: Performance indexes
```
## 📊 Code Quality & Testing
### Test Coverage
- `env_test.go` - Base64 environment variable handling (8 test cases)
- `dpop_test.go` - DPoP proof structure validation
- `oauth_test.go` - Integration tests for OAuth endpoints
### Linter Compliance
- Fixed errcheck violations (defer close, error handling)
- Formatted with gofmt
- Added nolint directives where appropriate
### Constants & Configuration
- [constants.go](internal/api/handlers/oauth/constants.go) - Named configuration values
- `SessionMaxAge = 7 * 24 * 60 * 60`
- `TokenRefreshThreshold = 5 * time.Minute`
- `MinCookieSecretLength = 32`
## 🎓 Implementation Decisions
### Custom OAuth vs Indigo Library
- **Decision**: Implement custom OAuth client
- **Rationale**: Indigo OAuth library explicitly unstable; custom implementation gives full control over edge cases and nonce retry logic
- **Future**: Migrate when indigo reaches stable v1.0
### Session Storage
- **Decision**: PostgreSQL with one session per DID
- **Rationale**: Simple for initial implementation, easy to upgrade to multiple sessions later, transaction support
### DPoP Key Management
- **Decision**: Unique key per session, stored in database
- **Rationale**: RFC 9449 compliance, token binding security, survives server restarts
## 📈 Performance Optimizations
- `idx_oauth_sessions_did_expires` - Fast session expiry queries
- Partial index for active sessions (`WHERE expires_at > NOW()`)
- Hourly cleanup prevents table bloat
- Cookie store singleton reduces memory allocations
## ✅ Production Readiness
### Real-World Validation
✅ Successfully tested with live PDS: `https://pds.bretton.dev`
✅ Handle resolution: `bretton.dev` → DID → PDS discovery
✅ Complete authorization flow with DPoP nonce retry
✅ Session storage and retrieval validated
✅ Token refresh logic confirmed working
### Security Checklist
✅ DPoP token binding prevents theft/replay
✅ PKCE prevents authorization code interception
✅ PAR reduces attack surface
✅ Atomic state operations prevent CSRF
✅ HTTP-only, secure, SameSite cookies
✅ Private keys never exposed in public endpoints
✅ Automatic token expiration (60 min access, ~90 day refresh)
## 📦 Files Changed
- **27 files**: 3,130 additions, 1 deletion
- **New packages**: oauth handlers, OAuth client, auth middleware
- **New migrations**: OAuth tables + indexes
- **Updated**: main.go (OAuth initialization), .env.dev (configuration docs)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Core Features
### Identity Resolution System (internal/atproto/identity/)
- Implement DNS/HTTPS handle resolution using Bluesky Indigo library
- Add DID document resolution via PLC directory
- Create PostgreSQL-backed caching layer (24h TTL)
- Support bidirectional caching (handle ↔ DID)
- Add atomic cache purge with single-query CTE optimization
### Database Schema
- Add identity_cache table with timezone-aware timestamps
- Support handle normalization via database trigger
- Enable automatic expiry checking
### Handle Update System
- Add UpdateHandle method to UserService and UserRepository
- Implement handle change detection in Jetstream consumer
- Update database BEFORE purging cache (prevents race condition)
- Purge BOTH old handle and DID entries on handle changes
- Add structured logging for cache operations
### Bug Fixes
- Fix timezone bugs throughout codebase (use UTC consistently)
- Fix rate limiter timestamp handling
- Resolve pre-existing test isolation bug in identity cache tests
- Fix Makefile test command to exclude restricted directories
### Testing
- Add comprehensive identity resolution test suite (450+ lines)
- Add handle change integration test with cache verification
- All 46+ integration test subtests passing
- Test both local and real atProto handle resolution
### Configuration
- Add IDENTITY_PLC_URL and IDENTITY_CACHE_TTL env vars
- Add golangci-lint configuration
- Update Makefile to avoid permission denied errors
## Architecture Decisions
- Use decorator pattern for caching resolver
- Maintain layer separation (no SQL in handlers)
- Reject database triggers for cache invalidation (keeps logic in app layer)
- Follow atProto best practices from QuickDID
## Files Changed
- 7 new files (identity system + migration + tests)
- 12 modified files (integration + bug fixes)
- ~800 lines of production code
- ~450 lines of tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Groups all atProto-related code under internal/atproto/ for better
code organization:
internal/
├── atproto/
│ ├── lexicon/ # Protocol definitions
│ └── jetstream/ # Firehose consumer
├── core/ # Business logic
└── db/ # Persistence
Changes:
- Moved internal/jetstream/ → internal/atproto/jetstream/
- Updated import paths in cmd/server/main.go
- Updated import paths in all test files
All tests passing:
✅ Unit tests
✅ Integration tests
✅ E2E tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
IDE configuration files should not be tracked in git.
These files are already in .gitignore but were committed before
the ignore rule was added.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Addresses PR review feedback with security, validation, and reliability improvements.
## Security & Validation Improvements
- Add lexicon-compliant error types (InvalidHandle, WeakPassword, etc.)
- Implement official atProto handle validation per spec
- Normalizes to lowercase before validation
- Validates TLD restrictions (.local, .onion, etc. disallowed)
- Max 253 char length enforcement
- Reference: https://atproto.com/specs/handle
- Add password validation (min 8 chars)
- Protects PDS from spam by malicious third-party clients
- PDS remains authoritative on final acceptance
- Add HTTP client timeout (10s) to prevent hanging on slow PDS
- Map service errors to proper XRPC error responses with correct status codes
## Test Reliability Improvements
- Replace fixed time.Sleep() with retry-with-timeout pattern
- Inline retry loops with 500ms polling intervals
- Configurable deadlines per test scenario (10-15s)
- 2x faster test execution on fast systems
- More reliable on slow CI environments
- Add E2E test database setup helper
- Fix test expectations to match new error messages
## Architecture Documentation
- Add TODO comments for future improvements:
- Race condition in Jetstream consumer (sync.Once needed)
- DID→PDS URL resolution via PLC directory for federation
- Document that current implementation works for local dev
- Mark federation support as future enhancement
## Files Changed
New files:
- internal/core/users/errors.go - Domain error types
- tests/e2e/user_signup_test.go - Full E2E test coverage
- internal/atproto/lexicon/social/coves/actor/signup.json - Lexicon spec
- docs/E2E_TESTING.md - E2E testing guide
- internal/jetstream/user_consumer.go - Event consumer
- tests/integration/jetstream_consumer_test.go - Consumer tests
- tests/integration/user_test.go - User service tests
Modified:
- internal/core/users/service.go - Enhanced validation + HTTP timeout
- internal/api/routes/user.go - Lexicon error mapping
- tests/integration/user_test.go - Updated test expectations
## Test Results
✅ All unit/integration tests pass
✅ Full E2E test suite passes (10.3s)
✅ Validates complete signup flow: XRPC → PDS → Jetstream → AppView → PostgreSQL
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements a minimal, production-ready user management system for Coves
with atProto DID-based identity and comprehensive security improvements.
## Core Features
- atProto-compliant user model (DID + handle)
- Single clean migration (001_create_users_table.sql)
- XRPC endpoint: social.coves.actor.getProfile
- Handle-based authentication (resolves handle → DID)
- PostgreSQL AppView indexing
## Security & Performance Fixes
- **Rate limiting**: 100 req/min per IP (in-memory middleware)
- **Input validation**: atProto handle regex validation
- Alphanumeric + hyphens + dots only
- No consecutive hyphens, must start/end with alphanumeric
- 1-253 character length limit
- **Database constraints**: Proper unique constraint error handling
- Clear error messages for duplicate DID/handle
- No internal details leaked to API consumers
- **Performance**: Removed duplicate DB checks (3 calls → 1 call)
## Breaking Changes
- Replaced email/username model with DID/handle
- Deleted legacy migrations (001, 005)
- Removed old repository and service test files
## Architecture
- Repository: Parameterized queries, context-aware
- Service: Business logic with proper validation
- Handler: Minimal XRPC implementation
- Middleware: Rate limiting for public endpoints
## Testing
- Full integration test coverage (4 test suites, all passing)
- Duplicate creation validation tests
- Handle format validation (9 edge cases)
- XRPC endpoint tests (success/error scenarios)
## Documentation
- Updated TESTING_SUMMARY.md with .test handle convention
- Added TODO for federated PDS support
- RFC3339 timestamp formatting
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Consolidate all test and development configuration into a single source
of truth (.env.dev) for cleaner, more maintainable setup.
Changes:
- Remove obsolete .env.test and .env.test.example files
- Update Makefile to load .env.dev variables automatically via include
- Simplify test commands (no more bash subshells or complex sourcing)
- Update integration tests to read config from environment variables
- Rewrite TESTING_SUMMARY.md with current unified approach
- Update LOCAL_DEVELOPMENT.md to reference single config file
Benefits:
- Single source of truth for all configuration
- Simpler test execution: just `make test`
- Isolated test DB (port 5434) separate from dev (port 5433)
- Better documentation and developer experience
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Simplified Configuration:**
- Test database credentials now in .env.dev (single source of truth)
- docker-compose.dev.yml uses env vars for test DB (POSTGRES_TEST_*)
- Makefile sources .env.dev for all test commands
- No need for separate .env.test file
**Removed:**
- run-tests.sh - Redundant, use `make test` instead
**Benefits:**
- All local dev config in one place (.env.dev)
- Less mental overhead (dev + test in same file)
- Consistent variable usage throughout stack
- Simpler developer onboarding
Usage:
- `make test` - Run all tests (handles DB automatically)
- All test config in .env.dev under "Test Database Configuration"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major cleanup now that PDS handles all repository operations:
**Removed:**
- internal/core/repository/ - Repository domain logic (PDS handles this)
- internal/db/postgres/repository_repo.go - Repository database operations
- internal/api/handlers/repository_handler.go - Repository API handlers
- internal/api/routes/repository.go - Repository routes
- tests/integration/repository_test.go - Repository integration tests
- Migrations 002, 003, 004 - Repository/CAR storage tables
- internal/db/local_dev_db_compose/ - Separate dev database setup
- internal/db/test_db_compose/ - Separate test database setup
**Unified:**
- docker-compose.dev.yml now includes PostgreSQL + PDS + optional test DB
- All database management moved to Makefile commands
- Consistent use of .env.dev variables throughout
**Updated:**
- cmd/server/main.go - Simplified to only use user service
- Makefile - All-in-one commands (dev-up starts both PostgreSQL + PDS)
- Added db-migrate, db-reset, test commands using Docker profiles
**Architecture:**
- PDS: Self-contained with SQLite + CAR files (port 3001)
- PostgreSQL: Only for Coves AppView indexing (port 5433)
- Test DB: Available via --profile test (port 5434)
- Single source of truth: docker-compose.dev.yml + .env.dev
Commands:
- `make dev-up` - Start PostgreSQL + PDS
- `make test` - Start test DB + run tests
- `make db-migrate` - Run migrations
- `make db-shell` - Open psql shell
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add docker-compose.dev.yml with Bluesky PDS (port 3001)
- Add .env.dev with development configuration
- Add Makefile with convenient dev commands (help, dev-up, dev-down, etc.)
- Add comprehensive docs/LOCAL_DEVELOPMENT.md guide
- Update CLAUDE.md and ATPROTO_GUIDE.md with correct architecture
- Remove custom carstore implementation (PDS handles this)
- Remove internal/atproto/repo wrapper (not needed)
- Add feed lexicon schemas (getAll, getCommunity, getTimeline)
- Update post lexicons to remove getFeed (replaced by feed queries)
- Update PROJECT_STRUCTURE.md to reflect new architecture
Architecture:
- PDS is self-contained with internal SQLite + CAR storage
- PostgreSQL database only used by Coves AppView for indexing
- AppView subscribes directly to PDS firehose (no relay needed for local dev)
- PDS runs on port 3001 to avoid conflicts with production PDS on 3000
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
feat: Implement union types for moderation configuration
- Replace single moderationConfig with union type supporting both moderator and sortition variants
- Add $type discriminator field following atProto patterns
- Separate tribunal-specific fields (tribunalThreshold, jurySize) to sortition variant only
- Update test files to use new union structure with proper $type fields
- Ensure type safety: only sortition communities can configure tribunal settings
This change improves the lexicon design by:
- Following atProto best practices for discriminated unions
- Providing clear separation between moderation types
- Enabling future extensibility for new moderation approaches
- Maintaining backwards compatibility through the union pattern
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
feat: Add comprehensive lexicon test data and validation fixes
- Add 35 test data files covering all lexicon record types
- Actor records: block, membership, preferences, profile, saved, subscription
- Community records: moderator, profile, rules, wiki
- Interaction records: comment, share, tag
- Moderation records: ruleProposal, tribunalVote, vote
- Post records: post validation
- Fix lexicon schema issues:
- Add 'handle' format to actor profile
- Fix typo in community profile moderation type
- Add required $type field to interaction comments
- Add minItems constraint to tag arrays
- Fix enum values in moderation schemas
- Improve validate-lexicon tool:
- Better number handling to prevent float64 conversion issues
- Add json.Number support for accurate integer validation
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
feat: Implement comprehensive lexicon validation system
- Add internal/validation/lexicon.go with ValidateLexiconData function
- Create validate-lexicon CLI tool for testing lexicon schemas
- Add comprehensive test suite with valid and invalid test cases
- Include test data for actors, communities, posts, interactions, and moderation
- Replace shell-based validation script with Go implementation
- Support for complex validation including enums, unions, and references
This provides a robust foundation for validating atProto lexicon data
in the Coves platform, ensuring data integrity and type safety.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
feat: Add domain knowledge documentation and update lexicon schemas
- Add comprehensive DOMAIN_KNOWLEDGE.md documenting Coves platform concepts
- Update CLAUDE.md with improved development guidelines
- Enhance actor preferences and subscription lexicon schemas
- Improve post retrieval and feed functionality with better tag support
- Update create-pr command documentation
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
feat: Unify post lexicon architecture and improve tag handling
Major architectural improvements to the post lexicon system:
- Unified 5 separate post types into single record with postType discriminator
- Moved tags from author-created content to community interactions (following Bluesky pattern)
- Added tagCounts to post stats and viewer tags to viewerState
- Cleaned up legacy fields (removed nsfw boolean, use contentLabels)
- Simplified embedType enum (image-gallery → image)
- Updated federation schema to use specific platform names
- Clarified title field as optional for certain post types
- Support for up to 8 images in image embeds
This change enables:
- Simpler codebase with single post record type
- Filtering by post type(s) at query level
- Community-driven tagging system
- Consistent embed handling across all post types
- Better federation support with originalAuthor field
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This reverts commit 32f6836b6ebb8af1a475b06c4045a545f06cdcff.
Major architectural improvements to the post lexicon system:
- Unified 5 separate post types into single record with postType discriminator
- Moved tags from author-created content to community interactions (following Bluesky pattern)
- Added tagCounts to post stats and viewer tags to viewerState
- Cleaned up legacy fields (removed nsfw boolean, use contentLabels)
- Simplified embedType enum (image-gallery → image)
- Updated federation schema to use specific platform names
- Clarified title field as optional for certain post types
- Support for up to 8 images in image embeds
This change enables:
- Simpler codebase with single post record type
- Filtering by post type(s) at query level
- Community-driven tagging system
- Consistent embed handling across all post types
- Better federation support with originalAuthor field
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
feat: Reimplement richtext using Bluesky-inspired facet system
- Fixed typo "filesred" → "files" in validate-lexicon/main.go
- Added validate-lexicon binary to .gitignore and removed from git
- Updated facet schema to include required $type fields for AT Protocol compatibility
- Removed duplicate mentions field from microblog.json (use facets instead)
- Added comprehensive facet tests covering UTF-8 byte counting and all feature types
- Fixed lexicon validation test to reference correct schema names
- Added detailed facet documentation with UTF-8 byte counting examples
The facet implementation now follows AT Protocol standards with proper $type
fields for each feature (mention, link, bold, italic, strikethrough, spoiler).
All byte indices use UTF-8 encoding for cross-platform consistency.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace separate markup/mention/link lexicons with unified facet approach
- Use byte-indexed positioning (byteStart/byteEnd) for text annotations
- Support overlapping features: bold, italic, strikethrough, spoiler, mention, link
- Add native support for community mentions with \! prefix
- Update all schemas to use *Facets fields instead of *Markup
- Align with AT Protocol standards for better federation compatibility
BREAKING CHANGE: All richtext markup fields renamed to facets
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement query and procedure lexicon definitions
This commit adds a complete set of lexicon definitions required for the
core functionality of the Coves social platform, following AT Protocol
best practices.
Added 29 new lexicon files across 4 namespaces:
Actor namespace (7 files):
- blockUser.json: Block another user
- getProfile.json: Retrieve user profile with stats
- preferences.json: User preferences record type
- saveItem.json: Save posts/comments for later
- unblockUser.json: Unblock a previously blocked user
- unsaveItem.json: Remove saved items
- updateProfile.json: Update user profile information
Community namespace (10 files):
- create.json: Create a new community
- get.json: Retrieve community details
- getMembers.json: List community members
- getSubscribers.json: List community subscribers
- list.json: List communities with filtering
- moderator.json: Moderator assignment record type
- search.json: Search communities by text
- subscribe.json: Subscribe to a community
- unsubscribe.json: Unsubscribe from a community
- update.json: Update community settings
Post namespace (6 files):
- create.json: Create posts (text, image, video, article, microblog)
- delete.json: Delete a post
- get.json: Retrieve a single post
- getFeed.json: Get post feeds with sorting options
- search.json: Search posts by text
- update.json: Update post content
Interaction namespace (4 files):
- createComment.json: Comment on posts/comments
- deleteComment.json: Delete comments
- createVote.json: Vote on posts/comments
- deleteVote.json: Remove votes
Bug fixes:
- Fixed duplicate 'createdAt' in membership.json required array
- Corrected getSaved.json type from 'procedure' to 'query'
All lexicons follow the established patterns:
- Queries for read operations
- Procedures for write operations
- Proper error definitions
- Consistent parameter naming
- Support for pagination where appropriate
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added infrastructure/technical TODOs:
- OAuth authentication for community actions (P1)
- Jetstream consumer race condition (P2)
- Structured logging migration (P3)
- PDS URL resolution from DID (P3)
- PLC directory registration for prod (P3)
Feature-specific TODOs (avatars, moderator checks, update/delete handlers)
are tracked in their respective PRDs (PRD_COMMUNITIES, PRD_GOVERNANCE).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem:
- Default INSTANCE_DID was did:web:coves.local
- instanceDomain extracted as "coves.local"
- Community handles generated as "{name}.communities.coves.local"
- .local TLD is disallowed per atProto spec (RFC 6762)
- Result: Community creation failed immediately with InvalidHandleError
Solution:
- Changed default to did:web:coves.social (.social is valid TLD)
- Added TODO comment documenting did:web domain verification security issue
- Created docs/PRD_BACKLOG.md to track follow-up work
Security Note:
Self-hosters can currently set INSTANCE_DID to any domain without
verification. This enables domain impersonation attacks. Added to
backlog (P0) to implement did:web verification per atProto spec.
Testing:
- All integration tests pass (TestCommunity_E2E)
- Community handles now: gaming.communities.coves.social ✓
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add logging for internal server errors in community XRPC handlers to
aid debugging during development. Internal errors are now logged before
returning generic error responses to clients.
Changes:
- Add log import to errors.go
- Log actual error details when returning InternalServerError
- Add TODO comment to migrate to proper structured logger
Rationale:
During E2E testing, internal errors were being silently swallowed making
debugging difficult. This change logs the actual error while still
returning safe generic error messages to clients.
Security note:
- Internal error details are NOT exposed to clients
- Logging is for server-side debugging only
- Generic "InternalServerError" message still returned to clients
TODO: Replace log.Printf with structured logger (e.g., zerolog/zap)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update development PostgreSQL port from 5433 to 5435 to avoid conflicts
with existing local PostgreSQL installations or other services.
Changes:
- .env.dev: Update POSTGRES_PORT to 5435
- docker-compose.dev.yml: Update health checks to use wget instead of curl
- cmd/server/main.go: Update default DATABASE_URL to use port 5435
Additional improvements:
- Replace curl with wget in Docker healthchecks (more reliable in Alpine)
- Update comments to reflect new port configuration
Ports summary:
- Dev PostgreSQL: 5435 (was 5433)
- Test PostgreSQL: 5434 (unchanged)
- PDS: 3001
- AppView: 8081
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive technical decisions to PRDs documenting architecture
choices for community handles and moderator record storage.
PRD_COMMUNITIES.md:
- Add technical decision: Single handle field (2025-10-11)
- Update lexicon summary to reflect DNS-valid handle approach
- Add DNS infrastructure checklist items (wildcard setup, well-known endpoint)
- Document that !name@instance format is client-side display only
PRD_GOVERNANCE.md:
- Add technical decision: Moderator records storage location (2025-10-11)
- Document security analysis comparing user repo vs community repo
- Explain attack vector for malicious self-hosted instances
- Rationale: Community repo provides better security and federation
Key decisions documented:
1. Single handle field matches Bluesky pattern (app.bsky.actor.profile)
2. Separation of concerns: protocol (DNS handle) vs presentation (!prefix)
3. Moderator records in community repo prevents forgery attacks
4. DNS wildcard required for *.communities.coves.social resolution
Infrastructure requirements added:
- [ ] DNS Wildcard Setup: Configure *.communities.coves.social
- [ ] Well-Known Endpoint: Implement .well-known/atproto-did handler
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update all community tests to use DNS-valid atProto handles instead of
scoped handle format. All tests passing including E2E, integration, and
unit test suites.
Changes:
- Update test fixtures to use DNS-valid handles
- Remove atprotoHandle references from test data
- Rename TestCommunityConsumer_AtprotoHandleField to TestCommunityConsumer_HandleField
- Update test assertions to expect DNS format handles
- Fix unused variable warnings in unit tests
Test coverage:
✅ E2E tests (5.57s) - Full PDS → Jetstream → AppView flow
✅ Integration tests (4.36s) - 13 suites covering CRUD, credentials, V2 validation
✅ Unit tests (0.37s) - Service layer, timeout handling, credentials
✅ Lexicon validation (0.40s) - All 60 schemas validated
Example test data changes:
- Before: handle="!gaming@coves.social"
- After: handle="gaming.communities.coves.social"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update community service and consumer to work with single handle field.
Remove scoped handle generation (!name@instance) and store DNS-valid
atProto handle directly.
Changes:
- Remove scoped handle generation logic
- Update handle validation regex to accept DNS format
- Store pdsAccount.Handle directly (e.g., gaming.communities.coves.social)
- Consumer uses handle field directly from profile record
- Update comments to reflect single handle approach
Technical details:
- Regex now validates standard DNS hostname format (RFC 1035)
- Allows subdomain format: name.communities.instance.com
- Client UI will derive !name@instance display from name + instance
Impact:
- All E2E tests passing with real PDS and Jetstream
- Handle resolution works correctly
- Community creation/update flows validated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove redundant atprotoHandle field in favor of single DNS-resolvable
handle field. This matches Bluesky's pattern (app.bsky.actor.profile)
and follows atProto best practices.
Changes:
- Remove atprotoHandle field from social.coves.community.profile
- Update handle field description to indicate DNS-resolvable format
- Add format: "handle" validation
- Update test data to use DNS-valid handles
Rationale:
- Single source of truth for community handle
- Reduces confusion about which handle is "real"
- Client-side UI derives display format (!name@instance) from name + instance
- Follows separation of concerns: protocol vs presentation layer
Example:
- Before: handle="!gaming@coves.social", atprotoHandle="gaming.communities.coves.social"
- After: handle="gaming.communities.coves.social", display derived client-side
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated PDS_SERVICE_HANDLE_DOMAINS in .env.dev to include both:
- .local.coves.dev (for user accounts)
- .communities.coves.social (for V2 community accounts)
This allows E2E tests to successfully create community PDS accounts with
the proper subdomain. Required for V2 Communities architecture where each
community owns its own PDS account and repository.
Also added PRD_GOVERNANCE.md documenting governance mechanisms.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**PROBLEM**: Local PostgreSQL system service running on port 5433,
preventing dev docker-compose stack from starting.
**FIX**: Changed coves-dev-postgres port mapping from 5433→5432 to 5435→5432
**ENVIRONMENT PORTS**:
- Local PostgreSQL system: 5432, 5433
- Test PostgreSQL (docker): 5434
- Dev PostgreSQL (docker): 5435 (NEW)
- PDS (docker): 3001
**ALSO ADDED**:
- PDS_INVITE_REQUIRED: "false" (disable invite codes for testing)
- Updated PDS_SERVICE_HANDLE_DOMAINS to include .communities.coves.social
(allows community handles like community-name.communities.coves.social)
**IMPACT**:
- Dev stack can now start without port conflicts
- E2E tests can create community PDS accounts
- No changes required to test database setup
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**COMPREHENSIVE UPDATE**:
Restructured PRD_COMMUNITIES.md to focus on:
1. ✅ What's been implemented and tested
2. ⏳ What's in progress
3. 📋 What's remaining before V1 launch
**COMPLETED WORK** (documented):
- V2 Architecture (communities own PDS accounts)
- Credential management (persistence + encryption)
- Jetstream consumer (real-time firehose indexing)
- Repository layer (PostgreSQL with atomic operations)
- XRPC endpoints (create, get, update, list)
- Comprehensive test coverage
**CRITICAL FIXES** (documented):
- P0: PDS credential persistence
- P0: UpdateCommunity authentication
- V2 enforcement (removed V1 compatibility)
- Encryption at rest
- Dynamic timeouts
**ROADMAP** (documented):
- OAuth flows (in progress)
- Rate limiting and visibility enforcement
- Posts in communities
- Moderation tools
- Federation improvements
**CHANGES**:
- Removed code examples (kept PRD focused on status)
- Added "Recent Critical Fixes" section
- Organized by implementation status
- Clear V1 launch checklist
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**NEW TESTS**:
1. **community_credentials_test.go** - Integration tests:
- TestCommunityRepository_CredentialPersistence
* Verify PDS credentials saved to database
* Verify credentials retrievable after creation
- TestCommunityRepository_EncryptedCredentials
* Verify credentials encrypted in database (not plaintext)
* Verify decryption works correctly on retrieval
- TestCommunityRepository_V2OwnershipModel
* Verify owner_did == did (self-owned)
2. **community_v2_validation_test.go** - Integration tests:
- TestCommunityConsumer_V2RKeyValidation
* Accept rkey="self" (V2 communities)
* Reject rkey with TID pattern (V1 communities)
* Reject custom rkeys
- TestCommunityConsumer_AtprotoHandleField
* Verify handle field handling in consumer
3. **community_service_test.go** - Unit tests:
- TestCommunityService_PDSTimeouts
* Verify write ops use 30s timeout
* Verify read ops use 10s timeout
- TestCommunityService_UpdateWithCredentials
* Verify UpdateCommunity fetches credentials from DB
* Verify error if credentials missing
- TestCommunityService_CredentialPersistence
* Verify repo.Create() called with credentials
4. **community_e2e_test.go** - Enhanced E2E tests:
- Fixed .local TLD issue (changed to .social)
- Fixed handle length issue (use shorter test names)
- Complete flow: Service → PDS → Jetstream → Consumer → DB → XRPC
**TEST COVERAGE**:
- ✅ P0 credential persistence bug
- ✅ P0 UpdateCommunity authentication bug
- ✅ Encryption at rest
- ✅ V2 rkey validation
- ✅ Dynamic timeout logic
- ✅ End-to-end write-forward flow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**SECURITY ENHANCEMENT**: Encrypt community PDS access/refresh tokens
in PostgreSQL using pgcrypto extension.
**IMPLEMENTATION**:
1. **Migration 006**:
- Enable pgcrypto extension
- Create encryption_keys table with single 256-bit key
- Add encrypted BYTEA columns: pds_access_token_encrypted, pds_refresh_token_encrypted
- Generate random encryption key on first run (idempotent)
- Add index for communities with credentials
2. **Repository Layer**:
- Encrypt on INSERT using pgp_sym_encrypt()
- Decrypt on SELECT using pgp_sym_decrypt()
- Inline encryption/decryption (no application-layer crypto)
- Empty strings stored as NULL (skip encryption)
**KEY MANAGEMENT**:
- Single symmetric key stored in encryption_keys table
- Key persists across restarts via PostgreSQL storage
- Future: Support key rotation via rotated_at timestamp
**TRADE-OFFS**:
- Performance: Inline crypto adds ~1-2ms per query
- Security: Keys stored in same DB (acceptable for self-hosted)
- Simplicity: No external KMS required for initial version
**FUTURE ENHANCEMENTS**:
- External KMS integration (AWS KMS, Vault)
- Key rotation support
- Per-community keys (if needed)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**CONTEXT**: Pre-production system should not support V1 communities.
All communities must use V2 architecture from day one.
**CHANGES**:
1. **Jetstream Consumer** - Strict V2 validation:
- REJECT any community profile with rkey != "self"
- V1 used TID-based rkeys (e.g., "3km4..."), V2 uses "self"
- Removed V1 owner field handling
- Added clear error messages for V1 detection
2. **Lexicon Schema** - Removed V1 fields:
- Removed "owner" field (V1: owner != community DID)
- V2 principle: community IS the owner (self-owned)
3. **Domain Model** - Simplified ownership:
- Removed OwnerDID field from Community struct
- V2: owner_did always equals did (enforced at creation)
**V2 ARCHITECTURE PRINCIPLES**:
- Community owns its own PDS account (did)
- Community owns its own repository (at://did/...)
- Profile always at rkey="self" (not TID-based)
- Self-owned: owner_did == did (no separate owner)
**IMPACT**:
- Cleaner codebase without V1/V2 branching logic
- Prevents accidental V1 community creation
- Enforces architectural constraints at every layer
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**PROBLEM**: PDS credentials were never saved to database, making it impossible
to update community profiles later or re-authenticate if tokens expire.
**ROOT CAUSE**: After provisioning PDS account and creating profile record,
credentials were only stored in memory (returned Community struct) but never
persisted via repository.Create().
**FIX**: Call repo.Create() immediately after PDS provisioning to persist:
- pds_access_token
- pds_refresh_token
- pds_url
- did (from PDS createAccount response)
**IMPACT**:
- Communities can now be updated using their own credentials
- Token refresh will work when access tokens expire
- Critical for V2 write-forward architecture (community updates own profile)
**ARCHITECTURE**:
This fix enables the proper V2 flow:
1. Create community → Store credentials in DB
2. Update community → Fetch credentials from DB → Authenticate as community → Write-forward to PDS
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Test coverage:
- Repository layer: CRUD, subscriptions, search, pagination
- Consumer layer: Event handling, idempotency, filtering
- E2E: Write-forward → PDS → Firehose → Consumer → AppView → XRPC
E2E test validates:
- Full atProto write-forward architecture
- Real PDS integration (not mocked)
- Jetstream consumer indexing
- All XRPC HTTP endpoints
- Data consistency across layers
Test cleanup:
- Removed duplicate writeforward_test.go
- Removed incomplete xrpc_e2e_test.go
- Removed manual real_pds_test.go
- Kept only essential, non-overlapping tests
All tests passing ✅
- Initialize DID generator with PLC directory config
- Create Communities service with PDS connection
- Authenticate instance DID with PDS for write-forward
- Register XRPC HTTP routes
- Add graceful handling for PDS auth failures
Environment variables:
- IS_DEV_ENV: Enable dev mode (mock DID generation)
- PLC_DIRECTORY_URL: PLC directory endpoint
- PDS_URL: Personal Data Server URL
- PDS_INSTANCE_HANDLE: Instance handle for auth
- PDS_INSTANCE_PASSWORD: Instance password for auth
Endpoints implemented:
- GET /xrpc/social.coves.community.get - Retrieve by DID or handle
- GET /xrpc/social.coves.community.list - List with filters
- GET /xrpc/social.coves.community.search - Full-text search
- POST /xrpc/social.coves.community.create - Create community
- POST /xrpc/social.coves.community.subscribe - Subscribe to feed
- POST /xrpc/social.coves.community.unsubscribe - Unsubscribe
Security notes:
- TODO(Communities-OAuth): Authentication currently client-controlled
- MUST integrate OAuth middleware before production
- Authorization enforced at service layer
- Proper error mapping to HTTP status codes
Handles community events from firehose:
- Create/Update/Delete community profiles
- Subscribe/Unsubscribe events
- Uses atomic transaction methods for consistency
Key features:
- Idempotent event handling for replay safety
- Extracts community DID from record (not repo owner)
- Uses SubscribeWithCount for atomic count updates
- Proper error handling with graceful degradation
- Logs all indexed events for observability
Database schema:
- communities table with pg_trgm indexes for fuzzy search
- community_subscriptions with composite index for lookups
- community_memberships with reputation tracking
- community_moderation (V2 prepared)
Repository features:
- Atomic SubscribeWithCount/UnsubscribeWithCount transactions
- Idempotent operations for Jetstream replay safety
- Full-text search with relevance filtering (>0.2 threshold)
- Pagination and filtering support
- Proper error handling with domain error mapping
Performance optimizations:
- Composite index on (user_did, community_did) for subscription lookups
- GIN indexes for trigram similarity search
- GREATEST(0, count - 1) prevents negative counts
- Domain types: Community, Subscription, Membership
- Service layer implements atProto write-forward architecture
- Creates records on PDS (not directly in DB)
- Repository interface for AppView indexing
- Domain errors with proper categorization
- Authorization checks for community updates
Key architectural decisions:
- Communities are instance-scoped (V1)
- Records stored in instance's repo on PDS
- Community DID is metadata, not repo owner
- Service writes to PDS → Firehose → Consumer → AppView DB
This commit implements a complete, secure OAuth 2.0 + atProto authentication system
for Coves, including comprehensive security fixes based on code review.
## 📋 Core Features
### OAuth 2.0 + atProto Authentication
- **DPoP Token Binding (RFC 9449)**: Each session has unique cryptographic key
- **PKCE (RFC 7636)**: S256 challenge method prevents code interception
- **PAR (RFC 9126)**: Pre-registration of authorization requests
- **Complete OAuth Flow**: Login → Authorize → Callback → Session Management
### Implementation Architecture
- **Handlers**: [internal/api/handlers/oauth/](internal/api/handlers/oauth/)
- `login.go` - Initiates OAuth flow with handle resolution
- `callback.go` - Processes authorization code and creates session
- `logout.go` - Session termination
- `metadata.go` - RFC 7591 client metadata endpoint
- `jwks.go` - Public key exposure (JWK Set)
- **OAuth Client**: [internal/atproto/oauth/](internal/atproto/oauth/)
- `client.go` - OAuth HTTP client with PAR, token exchange, refresh
- `dpop.go` - DPoP proof generation (ES256 signatures)
- `pkce.go` - PKCE challenge generation
- **Session Management**: [internal/core/oauth/](internal/core/oauth/)
- `session.go` - OAuth data models (OAuthRequest, OAuthSession)
- `repository.go` - PostgreSQL storage with atomic operations
- `auth_service.go` - Authentication business logic
- **Middleware**: [internal/api/middleware/auth.go](internal/api/middleware/auth.go)
- `RequireAuth` - Enforces authentication
- `OptionalAuth` - Loads user context if available
- Automatic token refresh (< 5 min to expiry)
### Database Schema
- **oauth_requests**: Temporary state during authorization flow (10-min TTL)
- **oauth_sessions**: Long-lived authenticated sessions
- **Indexes**: Performance optimizations for session queries
- **Auto-cleanup**: Trigger-based expiration handling
### DPoP Transport
- **HTTP RoundTripper**: [internal/atproto/xrpc/dpop_transport.go](internal/atproto/xrpc/dpop_transport.go)
- Automatic DPoP proof injection on all requests
- Nonce rotation handling (automatic retry on 401)
- PDS and auth server nonce tracking
## 🔐 Security Features (PR Review Hardening)
### Critical Security Fixes
✅ **CSRF/Replay Protection**: Atomic `GetAndDeleteRequest()` prevents state reuse
✅ **Cookie Secret Validation**: Enforced minimum 32 bytes for session security
✅ **Error Sanitization**: No internal error details exposed to users
✅ **HTTPS Enforcement**: Production-only HTTPS cookies with explicit localhost checks
✅ **Clean Architecture**: Business logic extracted to `AuthService` layer
### Additional Security Measures
✅ **No Token Leakage**: Never log response bodies containing credentials
✅ **Race-Free**: Fixed concurrent access to DPoP nonces with proper mutex handling
✅ **Input Validation**: Handle format checking, state parameter verification
✅ **Session Isolation**: One active session per DID (upgradeable to multiple)
✅ **Automatic Cleanup**: Hourly background job removes expired sessions/requests
### Token Binding & Proof-of-Possession
- Each session generates unique ES256 key pair
- Access tokens cryptographically bound to client
- DPoP proofs include:
- JWK header (public key)
- HTTP method and URL (prevents token replay)
- Access token hash (`ath` claim)
- JTI (unique token ID)
- Server nonce (when required)
## 🎯 Configuration & Setup
### Environment Variables
```bash
# OAuth Configuration (.env.dev)
OAUTH_PRIVATE_JWK=base64:... # Client private key (ES256)
OAUTH_COOKIE_SECRET=... # Session cookie secret (min 32 bytes)
APPVIEW_PUBLIC_URL=http://127.0.0.1:8081
```
### Base64 Encoding Support
- Helper: `GetEnvBase64OrPlain()` supports both plain and base64-encoded values
- Prevents shell escaping issues with JSON in environment variables
- Format: `OAUTH_PRIVATE_JWK=base64:eyJhbGci...` or plain JSON
### Cookie Store Singleton
- Global singleton initialized at startup: `oauth.InitCookieStore(secret)`
- Shared across all handlers for consistent session management
- Validates secret length on initialization
### Database Migration
```sql
-- Migration 003: OAuth tables
-- Migration 004: Performance indexes
```
## 📊 Code Quality & Testing
### Test Coverage
- `env_test.go` - Base64 environment variable handling (8 test cases)
- `dpop_test.go` - DPoP proof structure validation
- `oauth_test.go` - Integration tests for OAuth endpoints
### Linter Compliance
- Fixed errcheck violations (defer close, error handling)
- Formatted with gofmt
- Added nolint directives where appropriate
### Constants & Configuration
- [constants.go](internal/api/handlers/oauth/constants.go) - Named configuration values
- `SessionMaxAge = 7 * 24 * 60 * 60`
- `TokenRefreshThreshold = 5 * time.Minute`
- `MinCookieSecretLength = 32`
## 🎓 Implementation Decisions
### Custom OAuth vs Indigo Library
- **Decision**: Implement custom OAuth client
- **Rationale**: Indigo OAuth library explicitly unstable; custom implementation gives full control over edge cases and nonce retry logic
- **Future**: Migrate when indigo reaches stable v1.0
### Session Storage
- **Decision**: PostgreSQL with one session per DID
- **Rationale**: Simple for initial implementation, easy to upgrade to multiple sessions later, transaction support
### DPoP Key Management
- **Decision**: Unique key per session, stored in database
- **Rationale**: RFC 9449 compliance, token binding security, survives server restarts
## 📈 Performance Optimizations
- `idx_oauth_sessions_did_expires` - Fast session expiry queries
- Partial index for active sessions (`WHERE expires_at > NOW()`)
- Hourly cleanup prevents table bloat
- Cookie store singleton reduces memory allocations
## ✅ Production Readiness
### Real-World Validation
✅ Successfully tested with live PDS: `https://pds.bretton.dev`
✅ Handle resolution: `bretton.dev` → DID → PDS discovery
✅ Complete authorization flow with DPoP nonce retry
✅ Session storage and retrieval validated
✅ Token refresh logic confirmed working
### Security Checklist
✅ DPoP token binding prevents theft/replay
✅ PKCE prevents authorization code interception
✅ PAR reduces attack surface
✅ Atomic state operations prevent CSRF
✅ HTTP-only, secure, SameSite cookies
✅ Private keys never exposed in public endpoints
✅ Automatic token expiration (60 min access, ~90 day refresh)
## 📦 Files Changed
- **27 files**: 3,130 additions, 1 deletion
- **New packages**: oauth handlers, OAuth client, auth middleware
- **New migrations**: OAuth tables + indexes
- **Updated**: main.go (OAuth initialization), .env.dev (configuration docs)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Core Features
### Identity Resolution System (internal/atproto/identity/)
- Implement DNS/HTTPS handle resolution using Bluesky Indigo library
- Add DID document resolution via PLC directory
- Create PostgreSQL-backed caching layer (24h TTL)
- Support bidirectional caching (handle ↔ DID)
- Add atomic cache purge with single-query CTE optimization
### Database Schema
- Add identity_cache table with timezone-aware timestamps
- Support handle normalization via database trigger
- Enable automatic expiry checking
### Handle Update System
- Add UpdateHandle method to UserService and UserRepository
- Implement handle change detection in Jetstream consumer
- Update database BEFORE purging cache (prevents race condition)
- Purge BOTH old handle and DID entries on handle changes
- Add structured logging for cache operations
### Bug Fixes
- Fix timezone bugs throughout codebase (use UTC consistently)
- Fix rate limiter timestamp handling
- Resolve pre-existing test isolation bug in identity cache tests
- Fix Makefile test command to exclude restricted directories
### Testing
- Add comprehensive identity resolution test suite (450+ lines)
- Add handle change integration test with cache verification
- All 46+ integration test subtests passing
- Test both local and real atProto handle resolution
### Configuration
- Add IDENTITY_PLC_URL and IDENTITY_CACHE_TTL env vars
- Add golangci-lint configuration
- Update Makefile to avoid permission denied errors
## Architecture Decisions
- Use decorator pattern for caching resolver
- Maintain layer separation (no SQL in handlers)
- Reject database triggers for cache invalidation (keeps logic in app layer)
- Follow atProto best practices from QuickDID
## Files Changed
- 7 new files (identity system + migration + tests)
- 12 modified files (integration + bug fixes)
- ~800 lines of production code
- ~450 lines of tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Groups all atProto-related code under internal/atproto/ for better
code organization:
internal/
├── atproto/
│ ├── lexicon/ # Protocol definitions
│ └── jetstream/ # Firehose consumer
├── core/ # Business logic
└── db/ # Persistence
Changes:
- Moved internal/jetstream/ → internal/atproto/jetstream/
- Updated import paths in cmd/server/main.go
- Updated import paths in all test files
All tests passing:
✅ Unit tests
✅ Integration tests
✅ E2E tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Addresses PR review feedback with security, validation, and reliability improvements.
## Security & Validation Improvements
- Add lexicon-compliant error types (InvalidHandle, WeakPassword, etc.)
- Implement official atProto handle validation per spec
- Normalizes to lowercase before validation
- Validates TLD restrictions (.local, .onion, etc. disallowed)
- Max 253 char length enforcement
- Reference: https://atproto.com/specs/handle
- Add password validation (min 8 chars)
- Protects PDS from spam by malicious third-party clients
- PDS remains authoritative on final acceptance
- Add HTTP client timeout (10s) to prevent hanging on slow PDS
- Map service errors to proper XRPC error responses with correct status codes
## Test Reliability Improvements
- Replace fixed time.Sleep() with retry-with-timeout pattern
- Inline retry loops with 500ms polling intervals
- Configurable deadlines per test scenario (10-15s)
- 2x faster test execution on fast systems
- More reliable on slow CI environments
- Add E2E test database setup helper
- Fix test expectations to match new error messages
## Architecture Documentation
- Add TODO comments for future improvements:
- Race condition in Jetstream consumer (sync.Once needed)
- DID→PDS URL resolution via PLC directory for federation
- Document that current implementation works for local dev
- Mark federation support as future enhancement
## Files Changed
New files:
- internal/core/users/errors.go - Domain error types
- tests/e2e/user_signup_test.go - Full E2E test coverage
- internal/atproto/lexicon/social/coves/actor/signup.json - Lexicon spec
- docs/E2E_TESTING.md - E2E testing guide
- internal/jetstream/user_consumer.go - Event consumer
- tests/integration/jetstream_consumer_test.go - Consumer tests
- tests/integration/user_test.go - User service tests
Modified:
- internal/core/users/service.go - Enhanced validation + HTTP timeout
- internal/api/routes/user.go - Lexicon error mapping
- tests/integration/user_test.go - Updated test expectations
## Test Results
✅ All unit/integration tests pass
✅ Full E2E test suite passes (10.3s)
✅ Validates complete signup flow: XRPC → PDS → Jetstream → AppView → PostgreSQL
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements a minimal, production-ready user management system for Coves
with atProto DID-based identity and comprehensive security improvements.
## Core Features
- atProto-compliant user model (DID + handle)
- Single clean migration (001_create_users_table.sql)
- XRPC endpoint: social.coves.actor.getProfile
- Handle-based authentication (resolves handle → DID)
- PostgreSQL AppView indexing
## Security & Performance Fixes
- **Rate limiting**: 100 req/min per IP (in-memory middleware)
- **Input validation**: atProto handle regex validation
- Alphanumeric + hyphens + dots only
- No consecutive hyphens, must start/end with alphanumeric
- 1-253 character length limit
- **Database constraints**: Proper unique constraint error handling
- Clear error messages for duplicate DID/handle
- No internal details leaked to API consumers
- **Performance**: Removed duplicate DB checks (3 calls → 1 call)
## Breaking Changes
- Replaced email/username model with DID/handle
- Deleted legacy migrations (001, 005)
- Removed old repository and service test files
## Architecture
- Repository: Parameterized queries, context-aware
- Service: Business logic with proper validation
- Handler: Minimal XRPC implementation
- Middleware: Rate limiting for public endpoints
## Testing
- Full integration test coverage (4 test suites, all passing)
- Duplicate creation validation tests
- Handle format validation (9 edge cases)
- XRPC endpoint tests (success/error scenarios)
## Documentation
- Updated TESTING_SUMMARY.md with .test handle convention
- Added TODO for federated PDS support
- RFC3339 timestamp formatting
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Consolidate all test and development configuration into a single source
of truth (.env.dev) for cleaner, more maintainable setup.
Changes:
- Remove obsolete .env.test and .env.test.example files
- Update Makefile to load .env.dev variables automatically via include
- Simplify test commands (no more bash subshells or complex sourcing)
- Update integration tests to read config from environment variables
- Rewrite TESTING_SUMMARY.md with current unified approach
- Update LOCAL_DEVELOPMENT.md to reference single config file
Benefits:
- Single source of truth for all configuration
- Simpler test execution: just `make test`
- Isolated test DB (port 5434) separate from dev (port 5433)
- Better documentation and developer experience
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Simplified Configuration:**
- Test database credentials now in .env.dev (single source of truth)
- docker-compose.dev.yml uses env vars for test DB (POSTGRES_TEST_*)
- Makefile sources .env.dev for all test commands
- No need for separate .env.test file
**Removed:**
- run-tests.sh - Redundant, use `make test` instead
**Benefits:**
- All local dev config in one place (.env.dev)
- Less mental overhead (dev + test in same file)
- Consistent variable usage throughout stack
- Simpler developer onboarding
Usage:
- `make test` - Run all tests (handles DB automatically)
- All test config in .env.dev under "Test Database Configuration"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major cleanup now that PDS handles all repository operations:
**Removed:**
- internal/core/repository/ - Repository domain logic (PDS handles this)
- internal/db/postgres/repository_repo.go - Repository database operations
- internal/api/handlers/repository_handler.go - Repository API handlers
- internal/api/routes/repository.go - Repository routes
- tests/integration/repository_test.go - Repository integration tests
- Migrations 002, 003, 004 - Repository/CAR storage tables
- internal/db/local_dev_db_compose/ - Separate dev database setup
- internal/db/test_db_compose/ - Separate test database setup
**Unified:**
- docker-compose.dev.yml now includes PostgreSQL + PDS + optional test DB
- All database management moved to Makefile commands
- Consistent use of .env.dev variables throughout
**Updated:**
- cmd/server/main.go - Simplified to only use user service
- Makefile - All-in-one commands (dev-up starts both PostgreSQL + PDS)
- Added db-migrate, db-reset, test commands using Docker profiles
**Architecture:**
- PDS: Self-contained with SQLite + CAR files (port 3001)
- PostgreSQL: Only for Coves AppView indexing (port 5433)
- Test DB: Available via --profile test (port 5434)
- Single source of truth: docker-compose.dev.yml + .env.dev
Commands:
- `make dev-up` - Start PostgreSQL + PDS
- `make test` - Start test DB + run tests
- `make db-migrate` - Run migrations
- `make db-shell` - Open psql shell
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add docker-compose.dev.yml with Bluesky PDS (port 3001)
- Add .env.dev with development configuration
- Add Makefile with convenient dev commands (help, dev-up, dev-down, etc.)
- Add comprehensive docs/LOCAL_DEVELOPMENT.md guide
- Update CLAUDE.md and ATPROTO_GUIDE.md with correct architecture
- Remove custom carstore implementation (PDS handles this)
- Remove internal/atproto/repo wrapper (not needed)
- Add feed lexicon schemas (getAll, getCommunity, getTimeline)
- Update post lexicons to remove getFeed (replaced by feed queries)
- Update PROJECT_STRUCTURE.md to reflect new architecture
Architecture:
- PDS is self-contained with internal SQLite + CAR storage
- PostgreSQL database only used by Coves AppView for indexing
- AppView subscribes directly to PDS firehose (no relay needed for local dev)
- PDS runs on port 3001 to avoid conflicts with production PDS on 3000
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace single moderationConfig with union type supporting both moderator and sortition variants
- Add $type discriminator field following atProto patterns
- Separate tribunal-specific fields (tribunalThreshold, jurySize) to sortition variant only
- Update test files to use new union structure with proper $type fields
- Ensure type safety: only sortition communities can configure tribunal settings
This change improves the lexicon design by:
- Following atProto best practices for discriminated unions
- Providing clear separation between moderation types
- Enabling future extensibility for new moderation approaches
- Maintaining backwards compatibility through the union pattern
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add 35 test data files covering all lexicon record types
- Actor records: block, membership, preferences, profile, saved, subscription
- Community records: moderator, profile, rules, wiki
- Interaction records: comment, share, tag
- Moderation records: ruleProposal, tribunalVote, vote
- Post records: post validation
- Fix lexicon schema issues:
- Add 'handle' format to actor profile
- Fix typo in community profile moderation type
- Add required $type field to interaction comments
- Add minItems constraint to tag arrays
- Fix enum values in moderation schemas
- Improve validate-lexicon tool:
- Better number handling to prevent float64 conversion issues
- Add json.Number support for accurate integer validation
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add internal/validation/lexicon.go with ValidateLexiconData function
- Create validate-lexicon CLI tool for testing lexicon schemas
- Add comprehensive test suite with valid and invalid test cases
- Include test data for actors, communities, posts, interactions, and moderation
- Replace shell-based validation script with Go implementation
- Support for complex validation including enums, unions, and references
This provides a robust foundation for validating atProto lexicon data
in the Coves platform, ensuring data integrity and type safety.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add comprehensive DOMAIN_KNOWLEDGE.md documenting Coves platform concepts
- Update CLAUDE.md with improved development guidelines
- Enhance actor preferences and subscription lexicon schemas
- Improve post retrieval and feed functionality with better tag support
- Update create-pr command documentation
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major architectural improvements to the post lexicon system:
- Unified 5 separate post types into single record with postType discriminator
- Moved tags from author-created content to community interactions (following Bluesky pattern)
- Added tagCounts to post stats and viewer tags to viewerState
- Cleaned up legacy fields (removed nsfw boolean, use contentLabels)
- Simplified embedType enum (image-gallery → image)
- Updated federation schema to use specific platform names
- Clarified title field as optional for certain post types
- Support for up to 8 images in image embeds
This change enables:
- Simpler codebase with single post record type
- Filtering by post type(s) at query level
- Community-driven tagging system
- Consistent embed handling across all post types
- Better federation support with originalAuthor field
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major architectural improvements to the post lexicon system:
- Unified 5 separate post types into single record with postType discriminator
- Moved tags from author-created content to community interactions (following Bluesky pattern)
- Added tagCounts to post stats and viewer tags to viewerState
- Cleaned up legacy fields (removed nsfw boolean, use contentLabels)
- Simplified embedType enum (image-gallery → image)
- Updated federation schema to use specific platform names
- Clarified title field as optional for certain post types
- Support for up to 8 images in image embeds
This change enables:
- Simpler codebase with single post record type
- Filtering by post type(s) at query level
- Community-driven tagging system
- Consistent embed handling across all post types
- Better federation support with originalAuthor field
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fixed typo "filesred" → "files" in validate-lexicon/main.go
- Added validate-lexicon binary to .gitignore and removed from git
- Updated facet schema to include required $type fields for AT Protocol compatibility
- Removed duplicate mentions field from microblog.json (use facets instead)
- Added comprehensive facet tests covering UTF-8 byte counting and all feature types
- Fixed lexicon validation test to reference correct schema names
- Added detailed facet documentation with UTF-8 byte counting examples
The facet implementation now follows AT Protocol standards with proper $type
fields for each feature (mention, link, bold, italic, strikethrough, spoiler).
All byte indices use UTF-8 encoding for cross-platform consistency.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace separate markup/mention/link lexicons with unified facet approach
- Use byte-indexed positioning (byteStart/byteEnd) for text annotations
- Support overlapping features: bold, italic, strikethrough, spoiler, mention, link
- Add native support for community mentions with \! prefix
- Update all schemas to use *Facets fields instead of *Markup
- Align with AT Protocol standards for better federation compatibility
BREAKING CHANGE: All richtext markup fields renamed to facets
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit adds a complete set of lexicon definitions required for the
core functionality of the Coves social platform, following AT Protocol
best practices.
Added 29 new lexicon files across 4 namespaces:
Actor namespace (7 files):
- blockUser.json: Block another user
- getProfile.json: Retrieve user profile with stats
- preferences.json: User preferences record type
- saveItem.json: Save posts/comments for later
- unblockUser.json: Unblock a previously blocked user
- unsaveItem.json: Remove saved items
- updateProfile.json: Update user profile information
Community namespace (10 files):
- create.json: Create a new community
- get.json: Retrieve community details
- getMembers.json: List community members
- getSubscribers.json: List community subscribers
- list.json: List communities with filtering
- moderator.json: Moderator assignment record type
- search.json: Search communities by text
- subscribe.json: Subscribe to a community
- unsubscribe.json: Unsubscribe from a community
- update.json: Update community settings
Post namespace (6 files):
- create.json: Create posts (text, image, video, article, microblog)
- delete.json: Delete a post
- get.json: Retrieve a single post
- getFeed.json: Get post feeds with sorting options
- search.json: Search posts by text
- update.json: Update post content
Interaction namespace (4 files):
- createComment.json: Comment on posts/comments
- deleteComment.json: Delete comments
- createVote.json: Vote on posts/comments
- deleteVote.json: Remove votes
Bug fixes:
- Fixed duplicate 'createdAt' in membership.json required array
- Corrected getSaved.json type from 'procedure' to 'query'
All lexicons follow the established patterns:
- Queries for read operations
- Procedures for write operations
- Proper error definitions
- Consistent parameter naming
- Support for pagination where appropriate
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>