Commit Graph

18 Commits

Author SHA1 Message Date
aa2ee5aea3 feat(#359): Encrypt LLM provider API keys in database
Implemented transparent encryption/decryption of LLM provider API keys
stored in llm_provider_instances.config JSON field using OpenBao Transit
encryption.

Implementation:
- Created llm-encryption.middleware.ts with encryption/decryption logic
- Auto-detects format (vault:v1: vs plaintext) for backward compatibility
- Idempotent encryption prevents double-encryption
- Registered middleware in PrismaService
- Created data migration script for active encryption
- Added migrate:encrypt-llm-keys command to package.json

Tests:
- 14 comprehensive unit tests
- 90.76% code coverage (exceeds 85% requirement)
- Tests create, read, update, upsert operations
- Tests error handling and backward compatibility

Migration:
- Lazy migration: New keys encrypted, old keys work until re-saved
- Active migration: pnpm --filter @mosaic/api migrate:encrypt-llm-keys
- No schema changes required
- Zero downtime

Security:
- Uses TransitKey.LLM_CONFIG from OpenBao Transit
- Keys never touch disk in plaintext (in-memory only)
- Transparent to LlmManagerService and providers
- Follows proven pattern from account-encryption.middleware.ts

Files:
- apps/api/src/prisma/llm-encryption.middleware.ts (new)
- apps/api/src/prisma/llm-encryption.middleware.spec.ts (new)
- apps/api/scripts/encrypt-llm-keys.ts (new)
- apps/api/prisma/migrations/20260207_encrypt_llm_api_keys/ (new)
- apps/api/src/prisma/prisma.service.ts (modified)
- apps/api/package.json (modified)

Note: The migration script (encrypt-llm-keys.ts) is not included in
tsconfig.json to avoid rootDir conflicts. It's executed via tsx which
handles TypeScript directly.

Refs #359

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:49:37 -06:00
864c23dc94 feat(#355): Create UserCredential model with RLS and encryption support
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Implements secure user credential storage with comprehensive RLS policies
and encryption-ready architecture for Phase 3 of M9-CredentialSecurity.

**Features:**
- UserCredential Prisma model with 19 fields
- CredentialType enum (6 values: API_KEY, OAUTH_TOKEN, etc.)
- CredentialScope enum (USER, WORKSPACE, SYSTEM)
- FORCE ROW LEVEL SECURITY with 3 policies
- Encrypted value storage (OpenBao Transit ready)
- Cascade delete on user/workspace deletion
- Activity logging integration (CREDENTIAL_* actions)
- 28 comprehensive test cases

**Security:**
- RLS owner bypass, user access, workspace admin policies
- SQL injection hardening for is_workspace_admin()
- Encryption version tracking ready
- Full down migration for reversibility

**Testing:**
- 100% enum coverage (all CredentialType + CredentialScope values)
- Unique constraint enforcement
- Foreign key cascade deletes
- Timestamp behavior validation
- JSONB metadata storage

**Files:**
- Migration: 20260207_add_user_credentials (184 lines + 76 line down.sql)
- Security: 20260207163740_fix_sql_injection_is_workspace_admin
- Tests: user-credential.model.spec.ts (28 tests, 544 lines)
- Docs: README.md (228 lines), scratchpad

Fixes #355

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:39:15 -06:00
737eb40d18 feat(#352): Encrypt existing plaintext Account tokens
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Implements transparent encryption/decryption of OAuth tokens via Prisma middleware with progressive migration strategy.

Core Implementation:
- Prisma middleware transparently encrypts tokens on write, decrypts on read
- Auto-detects ciphertext format: aes:iv:authTag:encrypted, vault:v1:..., or plaintext
- Uses existing CryptoService (AES-256-GCM) for encryption
- Progressive encryption: tokens encrypted as they're accessed/refreshed
- Zero-downtime migration (schema change only, no bulk data migration)

Security Features:
- Startup key validation prevents silent data loss if ENCRYPTION_KEY changes
- Secure error logging (no stack traces that could leak sensitive data)
- Graceful handling of corrupted encrypted data
- Idempotent encryption prevents double-encryption
- Future-proofed for OpenBao Transit encryption (Phase 2)

Token Fields Encrypted:
- accessToken (OAuth access tokens)
- refreshToken (OAuth refresh tokens)
- idToken (OpenID Connect ID tokens)

Backward Compatibility:
- Existing plaintext tokens readable (encryptionVersion = NULL)
- Progressive encryption on next write
- BetterAuth integration transparent (middleware layer)

Test Coverage:
- 20 comprehensive unit tests (89.06% coverage)
- Encryption/decryption scenarios
- Null/undefined handling
- Corrupted data handling
- Legacy plaintext compatibility
- Future vault format support
- All CRUD operations (create, update, updateMany, upsert)

Files Created:
- apps/api/src/prisma/account-encryption.middleware.ts
- apps/api/src/prisma/account-encryption.middleware.spec.ts
- apps/api/prisma/migrations/20260207_encrypt_account_tokens/migration.sql

Files Modified:
- apps/api/src/prisma/prisma.service.ts (register middleware)
- apps/api/src/prisma/prisma.module.ts (add CryptoService)
- apps/api/src/federation/crypto.service.ts (add key validation)
- apps/api/prisma/schema.prisma (add encryptionVersion)
- .env.example (document ENCRYPTION_KEY)

Fixes #352

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 13:16:43 -06:00
cf9a3dc526 feat(#350): Add RLS policies to auth tables with FORCE enforcement
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Implements Row-Level Security (RLS) policies on accounts and sessions tables with FORCE enforcement.

Core Implementation:
- Added FORCE ROW LEVEL SECURITY to accounts and sessions tables
- Created conditional owner bypass policies (when current_user_id() IS NULL)
- Created user-scoped access policies using current_user_id() helper
- Documented PostgreSQL superuser limitation with production deployment guide

Security Features:
- Prevents cross-user data access at database level
- Defense-in-depth security layer complementing application logic
- Owner bypass allows migrations and BetterAuth operations when no RLS context
- Production requires non-superuser application role (documented in migration)

Test Coverage:
- 22 comprehensive integration tests (9 accounts + 9 sessions + 4 context)
- Complete CRUD coverage: CREATE, READ, UPDATE, DELETE (own + others)
- Superuser detection with fail-fast error message
- Verification that blocked DELETE operations preserve data
- 100% test coverage, all tests passing

Integration:
- Uses RLS context provider from #351 (runWithRlsClient, getRlsClient)
- Parameterized queries using set_config() for security
- Transaction-scoped session variables with SET LOCAL

Files Created:
- apps/api/prisma/migrations/20260207_add_auth_rls_policies/migration.sql
- apps/api/src/auth/auth-rls.integration.spec.ts

Fixes #350

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 12:49:14 -06:00
Jason Woltje
ca4f5ec011 feat(#90): implement EVENT subscriptions for federation
Implement event pub/sub messaging for federation to enable real-time
event streaming between federated instances.

Features:
- Event subscription management (subscribe/unsubscribe)
- Event publishing to subscribed instances
- Event acknowledgment protocol
- Server-side event filtering based on subscriptions
- Full signature verification and connection validation

Implementation:
- FederationEventSubscription model for storing subscriptions
- EventService with complete event lifecycle management
- EventController with authenticated and public endpoints
- EventMessage, EventAck, and SubscriptionDetails types
- Comprehensive DTOs for all event operations

API Endpoints:
- POST /api/v1/federation/events/subscribe
- POST /api/v1/federation/events/unsubscribe
- POST /api/v1/federation/events/publish
- GET /api/v1/federation/events/subscriptions
- GET /api/v1/federation/events/messages
- POST /api/v1/federation/incoming/event (public)
- POST /api/v1/federation/incoming/event/ack (public)

Testing:
- 18 unit tests for EventService (89.09% coverage)
- 11 unit tests for EventController (83.87% coverage)
- All 29 tests passing
- Follows TDD red-green-refactor cycle

Technical Notes:
- Reuses existing FederationMessage model with eventType field
- Follows patterns from QueryService and CommandService
- Uses existing signature and connection infrastructure
- Supports hierarchical event type naming (e.g., "task.created")

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-03 13:45:00 -06:00
Jason Woltje
24d59e7595 feat(#65): implement full-text search with tsvector and GIN index
Add PostgreSQL full-text search infrastructure for knowledge entries:
- Add search_vector tsvector column to knowledge_entries table
- Create GIN index for fast full-text search performance
- Implement automatic trigger to maintain search_vector on insert/update
- Weight fields: title (A), summary (B), content (C)
- Update SearchService to use precomputed search_vector
- Add comprehensive integration tests for FTS functionality

Tests:
- 8/8 new integration tests passing
- 205/225 knowledge module tests passing
- All quality gates pass (typecheck, lint)

Refs #65

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 14:25:45 -06:00
Jason Woltje
ef25167c24 fix(#196): fix race condition in job status updates
Implemented optimistic locking with version field and SELECT FOR UPDATE
transactions to prevent data corruption from concurrent job status updates.

Changes:
- Added version field to RunnerJob schema for optimistic locking
- Created migration 20260202_add_runner_job_version_for_concurrency
- Implemented ConcurrentUpdateException for conflict detection
- Updated RunnerJobsService methods with optimistic locking:
  * updateStatus() - with version checking and retry logic
  * updateProgress() - with version checking and retry logic
  * cancel() - with version checking and retry logic
- Updated CoordinatorIntegrationService with SELECT FOR UPDATE:
  * updateJobStatus() - transaction with row locking
  * completeJob() - transaction with row locking
  * failJob() - transaction with row locking
  * updateJobProgress() - optimistic locking
- Added retry mechanism (3 attempts) with exponential backoff
- Added comprehensive concurrency tests (10 tests, all passing)
- Updated existing test mocks to support updateMany

Test Results:
- All 10 concurrency tests passing ✓
- Tests cover concurrent status updates, progress updates, completions,
  cancellations, retry logic, and exponential backoff

This fix prevents race conditions that could cause:
- Lost job results (double completion)
- Lost progress updates
- Invalid status transitions
- Data corruption under concurrent access

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 12:51:17 -06:00
Jason Woltje
7101864a15 fix(#189): add composite database index for job_events table
Add composite index [jobId, timestamp] to improve query performance
for the most common job_events access patterns.

Changes:
- Add @@index([jobId, timestamp]) to JobEvent model in schema.prisma
- Create migration 20260202122655_add_job_events_composite_index
- Add performance tests to validate index effectiveness
- Document index design rationale in scratchpad
- Fix lint errors in api-key.guard, herald.service, runner-jobs.service

Rationale:
The composite index [jobId, timestamp] optimizes the dominant query
pattern used across all services:
- JobEventsService.getEventsByJobId (WHERE jobId, ORDER BY timestamp)
- RunnerJobsService.streamEvents (WHERE jobId + timestamp range)
- RunnerJobsService.findOne (implicit jobId filter + timestamp order)

This index provides:
- Fast filtering by jobId (highly selective)
- Efficient timestamp-based ordering
- Optimal support for timestamp range queries
- Backward compatibility with jobId-only queries

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 12:30:19 -06:00
65b1dad64f feat(#164): Add database schema for job tracking
Add Prisma schema for runner jobs, job steps, and job events to support
the autonomous runner infrastructure (M4.2).

Enums added:
- RunnerJobStatus: PENDING, QUEUED, RUNNING, COMPLETED, FAILED, CANCELLED
- JobStepPhase: SETUP, EXECUTION, VALIDATION, CLEANUP
- JobStepType: COMMAND, AI_ACTION, GATE, ARTIFACT
- JobStepStatus: PENDING, RUNNING, COMPLETED, FAILED, SKIPPED

Models added:
- RunnerJob: Top-level job tracking linked to workspace and agent_tasks
- JobStep: Granular step tracking within jobs with phase organization
- JobEvent: Immutable event sourcing audit log for jobs and steps

Foreign key relationships:
- runner_jobs → workspaces (workspace_id, CASCADE)
- runner_jobs → agent_tasks (agent_task_id, SET NULL)
- job_steps → runner_jobs (job_id, CASCADE)
- job_events → runner_jobs (job_id, CASCADE)
- job_events → job_steps (step_id, CASCADE)

Indexes added for performance on workspace_id, status, priority, timestamp.

Migration: 20260201205935_add_job_tracking

Quality gates passed: typecheck, lint, build

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 21:01:57 -06:00
1e35e63444 feat(#128): add LlmProviderInstance Prisma schema
Added database schema for LLM provider instance configuration to support
multi-provider architecture.

Schema design:
- LlmProviderInstance model with UUID primary key
- Fields: providerType, displayName, userId, config, isDefault, isEnabled
- JSON config field for flexible provider-specific settings
- Nullable userId: NULL = system-level, UUID = user-level
- Foreign key to User with CASCADE delete
- Added llmProviders relation to User model

Indexes:
- user_id: Fast user lookup
- provider_type: Filter by provider
- is_default: Quick default lookup
- is_enabled: Enabled/disabled filtering

Migration: 20260131115600_add_llm_provider_instance
- PostgreSQL table creation with proper types
- Foreign key constraint
- Performance indexes

Prisma client regenerated successfully.
Database migration requires manual deployment when DB is available.

Fixes #128

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-31 11:57:40 -06:00
Jason Woltje
3ec2059470 feat: add semantic search with pgvector (closes #68, #69, #70)
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/pr/woodpecker Pipeline failed
Issues resolved:
- #68: pgvector Setup
  * Added pgvector vector index migration for knowledge_embeddings
  * Vector index uses HNSW algorithm with cosine distance
  * Optimized for 1536-dimension OpenAI embeddings

- #69: Embedding Generation Pipeline
  * Created EmbeddingService with OpenAI integration
  * Automatic embedding generation on entry create/update
  * Batch processing endpoint for existing entries
  * Async generation to avoid blocking API responses
  * Content preparation with title weighting

- #70: Semantic Search API
  * POST /api/knowledge/search/semantic - pure vector search
  * POST /api/knowledge/search/hybrid - RRF combined search
  * POST /api/knowledge/embeddings/batch - batch generation
  * Comprehensive test coverage
  * Full documentation in docs/SEMANTIC_SEARCH.md

Technical details:
- Uses OpenAI text-embedding-3-small model (1536 dims)
- HNSW index for O(log n) similarity search
- Reciprocal Rank Fusion for hybrid search
- Graceful degradation when OpenAI not configured
- Async embedding generation for performance

Configuration:
- Added OPENAI_API_KEY to .env.example
- Optional feature - disabled if API key not set
- Falls back to keyword search in hybrid mode
2026-01-30 15:19:13 -06:00
Jason Woltje
da4fb72902 feat: add agent task schema and CRUD API (closes #96, closes #97) 2026-01-29 23:26:22 -06:00
Jason Woltje
5dd46c85af feat(#82): implement Personality Module
- Add Personality model to Prisma schema with FormalityLevel enum
- Create migration and seed with 6 default personalities
- Implement CRUD API with TDD approach (97.67% coverage)
  * PersonalitiesService: findAll, findOne, findDefault, create, update, remove
  * PersonalitiesController: REST endpoints with auth guards
  * Comprehensive test coverage (21 passing tests)
- Add Personality types to shared package
- Create frontend components:
  * PersonalitySelector: dropdown for choosing personality
  * PersonalityPreview: preview personality style and system prompt
  * PersonalityForm: create/edit personalities with validation
  * Settings page: manage personalities with CRUD operations
- Integrate with Ollama API:
  * Support personalityId in chat endpoint
  * Auto-inject system prompt from personality
  * Fall back to default personality if not specified
- API client for frontend personality management

All tests passing with 97.67% backend coverage (exceeds 85% requirement)
2026-01-29 17:58:09 -06:00
Jason Woltje
5291fece26 feat(web): add workspace management UI (M2 #12)
- Create workspace listing page at /settings/workspaces
  - List all user workspaces with role badges
  - Create new workspace functionality
  - Display member count per workspace

- Create workspace detail page at /settings/workspaces/[id]
  - Workspace settings (name, ID, created date)
  - Member management with role editing
  - Invite member functionality
  - Delete workspace (owner only)

- Add workspace components:
  - WorkspaceCard: Display workspace info with role badge
  - WorkspaceSettings: Edit workspace settings and delete
  - MemberList: Display and manage workspace members
  - InviteMember: Send invitations with role selection

- Add WorkspaceMemberWithUser type to shared package
- Follow existing app patterns for styling and structure
- Use mock data (ready for API integration)
2026-01-29 16:59:26 -06:00
Jason Woltje
244e50c806 feat(multi-tenant): add Team model and RLS policies
Implements #9, #10
- Team model with workspace membership
- TeamMember model with role-based access (OWNER, ADMIN, MEMBER)
- Row-Level Security policies for tenant isolation on 19 tables
- Helper functions: current_user_id(), is_workspace_member(), is_workspace_admin()
- Developer utilities in src/lib/db-context.ts for easy RLS integration
- Comprehensive documentation in docs/design/multi-tenant-rls.md

Database migrations:
- 20260129220941_add_team_model: Adds Team and TeamMember tables
- 20260129221004_add_rls_policies: Enables RLS and creates policies

Security features:
- Complete database-level tenant isolation
- Automatic query filtering based on workspace membership
- Defense-in-depth security with application and database layers
- Performance-optimized with indexes on workspace_id
2026-01-29 16:13:09 -06:00
Jason Woltje
1edea83e38 feat(knowledge): add database schema for Knowledge Module
Implements KNOW-001
- KnowledgeEntry, Version, Link, Tag, Embedding models
- Full indexes and constraints
- Seed data for testing
2026-01-29 16:07:58 -06:00
Jason Woltje
973502f26e feat(#37-41): Add domains, ideas, relationships, agents, widgets schema
Schema additions for issues #37-41:

New models:
- Domain (#37): Life domains (work, marriage, homelab, etc.)
- Idea (#38): Brain dumps with pgvector embeddings
- Relationship (#39): Generic entity linking (blocks, depends_on)
- Agent (#40): ClawdBot agent tracking with metrics
- AgentSession (#40): Conversation session tracking
- WidgetDefinition (#41): HUD widget registry
- UserLayout (#41): Per-user dashboard configuration

Updated models:
- Task, Event, Project: Added domainId foreign key
- User, Workspace: Added new relations

New enums:
- IdeaStatus: CAPTURED, PROCESSING, ACTIONABLE, ARCHIVED, DISCARDED
- RelationshipType: BLOCKS, BLOCKED_BY, DEPENDS_ON, etc.
- AgentStatus: IDLE, WORKING, WAITING, ERROR, TERMINATED
- EntityType: Added IDEA, DOMAIN

Migration: 20260129182803_add_domains_ideas_agents_widgets
2026-01-29 12:29:21 -06:00
Jason Woltje
99afde4f99 feat(#2): Implement PostgreSQL 17 + pgvector database schema
Establishes multi-tenant database layer with vector similarity search for AI-powered memory features. Includes Docker infrastructure, Prisma ORM integration, NestJS services, and shared types across the monorepo.

Key changes:
- Docker: PostgreSQL 17 + pgvector v0.7.4, Valkey cache
- Schema: 8 models (User, Workspace, Task, Event, Project, ActivityLog, MemoryEmbedding) with RLS preparation
- NestJS: PrismaModule, DatabaseModule, EmbeddingsService
- Shared: Type-safe enums, constants, and database types

Fixes #2

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 16:06:34 -06:00