This guide helps developers migrate existing Lilith (consciousness/meditation platform) codebases to the unified Oshun monorepo.
Table of Contents#
- Overview
- Pre-Migration Checklist
- Architecture Changes
- Step-by-Step Migration
- Package Changes
- Database Migration
- API Changes
- Breaking Changes
- Event System
- Authentication
- Testing Updates
- Common Issues
Overview#
The Lilith platform is being consolidated into the Oshun monorepo to share infrastructure, authentication, and AI capabilities with other domains (Yemaya, Isis, Sophia, Hathor, Bellona).
What Changes#
| Aspect | Before | After |
|---|---|---|
| Repository | Standalone lilith repo |
oshun/apps/lilith/, oshun/libs/lilith/ |
| Package prefix | @lilith/* |
@lilith/* (unchanged) |
| Shared code | Copied/duplicated | @oshun/* shared libraries |
| Authentication | Lilith-specific auth | Unified @oshun/auth-primitives |
| Database | Separate Postgres | Shared cluster, separate schema |
| Events | Redis pub/sub | Unified @oshun/event-bus |
| AI Providers | Direct integrations | Unified through @isis/client |
What Stays the Same#
- Domain logic and business rules
- User-facing APIs and data structures
- Mobile app integration patterns
- Meditation content and courses
Breaking Changes#
The migration preserves Lilith's domain model, but it does introduce runtime and integration changes that require code updates:
| Area | Required Change | Compatibility Strategy |
|---|---|---|
| API routing | Route clients through /api/lilith/* behind the shared gateway |
Keep legacy routes only as temporary gateway redirects during cutover |
| Authentication | Replace Lilith-local tokens with unified Oshun JWT/session handling | Migrate sessions during the rollout window and reject unmigrated tokens after cutover |
| Event names | Move from colon-delimited events to namespaced domain events | Publish dual events only during migration; consumers must subscribe to lilith.* before launch |
| Shared packages | Import cross-cutting code from @oshun/* libraries |
Remove copied utilities after each package is migrated and covered by tests |
| Database access | Use the Lilith schema in the shared Postgres cluster | Run schema-qualified migrations and validate row counts before switching traffic |
These changes are intentionally explicit so callers, event consumers, and data migrations fail fast when old integration contracts remain in use.
Pre-Migration Checklist#
Before starting migration:
- Backup all databases and configurations
- Document all environment variables
- List all external service integrations
- Identify custom modifications to shared code
- Review current test coverage
- Set up Oshun monorepo locally
Local Development Environment Setup#
Before migrating, set up the local development environment:
# Clone the Oshun monorepo
git clone git@github.com:GreyChimp/oshun.git
cd oshun
# Start core infrastructure (PostgreSQL, Redis, MinIO, Mailpit)
docker compose -f docker/docker-compose.dev.yml up -d
# Verify all services are running
docker compose -f docker/docker-compose.dev.yml ps
# Install dependencies
pnpm install
# Create your local .env from template (already done if using docker setup)
cp docker/.env.example docker/.env
The development databases are automatically created:
lilith- Lilith domain database (PostgreSQL with pgvector)oshun_dev- Shared development database
Connection URL: postgresql://oshun:oshun_dev@localhost:5432/lilith
For detailed setup instructions, see CLAUDE.md.
Architecture Changes#
Old Architecture#
lilith-repo/
├── apps/
│ ├── api/ # Main API
│ ├── mobile/ # React Native app
│ ├── web/ # Web dashboard
│ └── workers/ # Background workers
├── packages/
│ ├── auth/ # Authentication
│ ├── database/ # Prisma client
│ ├── meditation-core/ # Core logic
│ ├── ai-integration/ # LLM integrations
│ └── common/ # Shared utilities
└── services/
├── notification/ # Push notifications
├── biometric/ # Health data
└── catalog/ # Content catalog
New Architecture#
oshun/
├── apps/lilith/
│ ├── svc-meditation-core/ # Core meditation service
│ ├── svc-auth-orchestrator/ # Auth coordination
│ ├── svc-notification/ # Push notifications
│ ├── svc-real-time-sync/ # WebSocket sync
│ ├── svc-catalog/ # Content catalog
│ ├── svc-daily-content/ # Daily recommendations
│ ├── svc-progress-sync/ # User progress
│ ├── svc-biometric/ # Health data integration
│ ├── svc-meditation-generation/ # AI meditation creation
│ └── ... (more services)
│
├── libs/lilith/
│ ├── sdk/ # TypeScript SDK
│ ├── partner-sdk/ # Partner integration SDK
│ ├── service-lib/ # Service utilities
│ ├── common/ # Lilith types/constants
│ └── event-handlers/ # Event subscriptions
│
└── libs/shared/ # Shared Oshun libraries
├── auth-primitives/ # ← Replaces packages/auth
├── database/ # ← Prisma utilities
├── ai/ # ← LLM abstraction
└── event-bus/ # ← Events
Step-by-Step Migration#
Step 1: Set Up Your Development Environment#
# Clone Oshun monorepo
git clone https://github.com/oshun-platform/oshun.git
cd oshun
# Install dependencies
pnpm install
# Copy environment variables
cp .env.example .env.local
Step 2: Migrate Shared Utilities#
Replace Lilith-specific utilities with Oshun shared libraries:
// BEFORE: Lilith-specific
import { createLogger } from '@lilith/logging';
import { createRedisClient } from '@lilith/cache';
import { AppError } from '@lilith/errors';
// AFTER: Oshun shared
import { createLogger } from '@oshun/logging';
import { createRedisClient } from '@oshun/cache';
import { AppError } from '@oshun/errors';
Step 3: Migrate Authentication#
// BEFORE: Lilith auth
import { verifyToken, createSession } from '@lilith/auth';
export async function authenticate(req: Request) {
const token = req.headers.authorization?.replace('Bearer ', '');
const user = await verifyToken(token);
return user;
}
// AFTER: Oshun auth
import { verifyJWT, createSession } from '@oshun/auth-primitives';
import { hasPermission } from '@oshun/identity';
export async function authenticate(req: Request) {
const token = req.headers.authorization?.replace('Bearer ', '');
const payload = await verifyJWT(token, {
issuer: 'oshun-auth',
audience: ['lilith'],
});
// Check domain-specific permissions
if (!hasPermission(payload, 'read:meditations')) {
throw new ForbiddenError('Insufficient permissions');
}
return payload;
}
Step 4: Migrate Database Access#
// BEFORE: Direct Prisma
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// AFTER: Oshun database utilities
import { createPrismaClient } from '@oshun/database';
const prisma = createPrismaClient({
schema: 'lilith',
logging: process.env.NODE_ENV === 'development',
});
Step 5: Migrate AI Integrations#
// BEFORE: Direct OpenAI calls
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
});
// AFTER: Use Isis for AI (maintains cost tracking, rate limits)
import { createIsisClient } from '@isis/client';
const isis = createIsisClient({ baseUrl: process.env.ISIS_API_URL });
// For text generation
const response = await isis.text.generate({
prompt,
model: 'gpt-4',
maxTokens: 1000,
});
// For meditation audio generation
const audio = await isis.audio.generate({
text: meditationScript,
voice: 'serene',
format: 'mp3',
});
Step 6: Migrate Event Handling#
// BEFORE: Direct Redis pub/sub
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
const subscriber = redis.duplicate();
subscriber.subscribe('meditation:completed');
subscriber.on('message', (channel, message) => {
const data = JSON.parse(message);
handleMeditationCompleted(data);
});
// AFTER: Oshun event bus
import { EventBus } from '@oshun/event-bus';
const eventBus = new EventBus({
redis: { url: process.env.REDIS_URL },
consumerGroup: 'lilith-service',
});
eventBus.subscribe('lilith.meditation.completed', async (event) => {
await handleMeditationCompleted(event.payload);
});
await eventBus.start();
Step 7: Update Service Entry Points#
// BEFORE: Express app
import express from 'express';
const app = express();
app.use(authMiddleware);
app.use('/api/meditations', meditationsRouter);
// AFTER: Hono with Oshun middleware
import { Hono } from 'hono';
import { authMiddleware, rateLimitMiddleware } from '@oshun/traefik-config';
import { metricsMiddleware } from '@oshun/metrics';
import { tracingMiddleware } from '@oshun/tracing';
const app = new Hono();
app.use('*', tracingMiddleware());
app.use('*', metricsMiddleware());
app.use('*', rateLimitMiddleware({ windowMs: 60000, max: 100 }));
app.use('/api/*', authMiddleware({ audience: 'lilith' }));
app.route('/api/meditations', meditationsRouter);
Step 8: Update Tests#
// BEFORE: Jest with custom mocks
import { mockRedis, mockPrisma } from '@lilith/test-utils';
describe('MeditationService', () => {
beforeEach(() => {
mockRedis.reset();
mockPrisma.reset();
});
it('should complete meditation', async () => {
// ...
});
});
// AFTER: Vitest with Oshun test utilities
import { describe, it, expect, beforeEach } from 'vitest';
import { createTestContext } from '@oshun/testing';
import { TestEventBus } from '@oshun/event-bus/testing';
describe('MeditationService', () => {
const ctx = createTestContext();
beforeEach(async () => {
await ctx.reset();
});
it('should complete meditation', async () => {
const eventBus = new TestEventBus();
// Test logic...
// Assert events were published
expect(eventBus.published).toContainEqual(
expect.objectContaining({
type: 'lilith.meditation.completed',
})
);
});
});
Package Changes#
Dependencies to Remove#
These are now provided by Oshun shared libraries:
{
"dependencies": {
"winston": "→ use @oshun/logging (pino)",
"ioredis": "→ use @oshun/cache",
"jsonwebtoken": "→ use @oshun/auth-primitives (jose)",
"openai": "→ use @isis/client",
"bullmq": "→ use @oshun/queue"
}
}
New Dependencies#
{
"dependencies": {
"@oshun/logging": "workspace:*",
"@oshun/errors": "workspace:*",
"@oshun/cache": "workspace:*",
"@oshun/database": "workspace:*",
"@oshun/auth-primitives": "workspace:*",
"@oshun/event-bus": "workspace:*",
"@oshun/metrics": "workspace:*",
"@oshun/tracing": "workspace:*",
"@isis/client": "workspace:*",
"@sophia/client": "workspace:*"
}
}
Import Path Changes#
| Old Import | New Import |
|---|---|
@lilith/logging |
@oshun/logging |
@lilith/auth |
@oshun/auth-primitives |
@lilith/database |
@oshun/database |
@lilith/cache |
@oshun/cache |
@lilith/queue |
@oshun/queue |
@lilith/ai |
@oshun/ai + @isis/client |
@lilith/common |
@lilith/common (keep) |
@lilith/meditation-core |
@lilith/service-lib |
Database Migration#
Schema Location#
Move Prisma schema to domain-specific location:
# Before
lilith-repo/prisma/schema.prisma
# After
oshun/libs/lilith/database/prisma/schema.prisma
Schema Changes#
// Update datasource
datasource db {
provider = "postgresql"
url = env("LILITH_DATABASE_URL")
schemas = ["lilith"]
}
// Add schema to all models
model User {
id String @id @default(uuid())
email String @unique
createdAt DateTime @default(now())
meditations Meditation[]
progress Progress[]
@@schema("lilith")
}
model Meditation {
id String @id @default(uuid())
title String
duration Int
audioUrl String?
userId String
user User @relation(fields: [userId], references: [id])
@@schema("lilith")
}
Data Migration#
# 1. Export existing data
pg_dump -h old-host -d lilith -f lilith_backup.sql
# 2. Create Lilith schema in Oshun cluster
psql -h oshun-db -d oshun -c "CREATE SCHEMA IF NOT EXISTS lilith;"
# 3. Restore with schema prefix
psql -h oshun-db -d oshun -c "SET search_path TO lilith;" -f lilith_backup.sql
# 4. Run Prisma migrations
cd libs/lilith/database
pnpm prisma migrate deploy
API Changes#
Endpoint Prefixes#
# Before
POST /api/meditations
GET /api/users/me/progress
POST /auth/login
# After (behind API Gateway)
POST /api/lilith/meditations
GET /api/lilith/users/me/progress
POST /api/auth/login (unified auth)
Response Format#
// Before: Custom format
{
"success": true,
"data": { ... },
"error": null
}
// After: Standard Oshun format
{
"data": { ... },
// or on error
"error": {
"code": "MEDITATION_NOT_FOUND",
"message": "Meditation not found",
"details": { "id": "med-123" }
},
"requestId": "req_abc123"
}
Error Handling#
// Use Oshun error types
import {
NotFoundError,
ValidationError,
UnauthorizedError,
} from '@oshun/errors';
export async function getMeditation(id: string) {
const meditation = await prisma.meditation.findUnique({ where: { id } });
if (!meditation) {
throw new NotFoundError('Meditation not found', { id });
}
return meditation;
}
Event System#
Event Type Migration#
| Old Event | New Event |
|---|---|
meditation:completed |
lilith.meditation.completed |
meditation:started |
lilith.meditation.started |
user:achievement |
lilith.achievement.earned |
progress:updated |
lilith.progress.updated |
session:created |
lilith.session.created |
Event Payload Updates#
// Before
{
type: 'meditation:completed',
userId: 'user-123',
meditationId: 'med-456',
duration: 600,
completedAt: '2024-01-15T10:30:00Z'
}
// After: Standard envelope
{
id: '550e8400-e29b-41d4-a716-446655440000',
type: 'lilith.meditation.completed',
source: 'lilith',
timestamp: '2024-01-15T10:30:00Z',
correlationId: 'req_abc123',
payload: {
userId: 'user-123',
meditationId: 'med-456',
sessionId: 'sess-789',
duration: 600,
completedAt: '2024-01-15T10:30:00Z'
},
metadata: {
deviceType: 'ios',
appVersion: '2.1.0'
}
}
Cross-Domain Events#
Now you can subscribe to events from other domains:
import { EventBus } from '@oshun/event-bus';
const eventBus = new EventBus({
/* config */
});
// Subscribe to Isis events for meditation audio generation
eventBus.subscribe('isis.audio.generated', async (event) => {
if (event.metadata.source === 'lilith') {
await attachAudioToMeditation(
event.metadata.meditationId,
event.payload.audioUrl
);
}
});
// Subscribe to Sophia for meditation content research
eventBus.subscribe('sophia.document.ingested', async (event) => {
if (event.payload.collection === 'meditation-research') {
await updateMeditationContent(event.payload);
}
});
Authentication#
User Migration#
Lilith users are migrated to the unified auth system:
// User model now references oshun_auth schema
model User {
id String @id @default(uuid())
authUserId String @unique // Reference to oshun_auth.users
// Lilith-specific profile data
displayName String?
avatarUrl String?
preferences Json @default("{}")
tier String @default("free")
@@schema("lilith")
}
Session Handling#
// Before: Lilith sessions
const session = await lilithAuth.createSession(user);
// After: Unified auth with Lilith claims
import { createSession } from '@oshun/auth-primitives';
const session = await createSession({
userId: user.id,
audience: ['lilith'],
claims: {
tier: user.tier,
permissions: ['read:meditations', 'write:progress'],
},
});
Mobile App Auth#
// Mobile apps use the unified OAuth flow
const authUrl =
`${AUTH_BASE}/oauth/authorize?` +
`client_id=${LILITH_CLIENT_ID}&` +
`redirect_uri=lilith://oauth/callback&` +
`response_type=code&` +
`scope=lilith:all&` +
`code_challenge=${codeChallenge}&` +
`code_challenge_method=S256`;
Testing Updates#
Test Configuration#
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['src/**/*.{test,spec}.ts'],
setupFiles: ['./test/setup.ts'],
},
});
Test Setup#
// test/setup.ts
import { beforeAll, afterAll, beforeEach } from 'vitest';
import { createTestDatabase } from '@oshun/testing';
import { TestEventBus } from '@oshun/event-bus/testing';
let testDb: Awaited<ReturnType<typeof createTestDatabase>>;
let testEventBus: TestEventBus;
beforeAll(async () => {
testDb = await createTestDatabase({ schema: 'lilith' });
testEventBus = new TestEventBus();
});
afterAll(async () => {
await testDb.cleanup();
});
beforeEach(async () => {
await testDb.reset();
testEventBus.clear();
});
export { testDb, testEventBus };
Mock Updates#
// Before
vi.mock('@lilith/auth', () => ({
verifyToken: vi.fn().mockResolvedValue({ userId: 'test-user' }),
}));
// After
vi.mock('@oshun/auth-primitives', () => ({
verifyJWT: vi.fn().mockResolvedValue({
sub: 'test-user',
aud: ['lilith'],
permissions: ['read:meditations'],
}),
}));
Common Issues#
Issue 1: Import Path Errors#
Error: Cannot find module '@lilith/logging'
Solution: Update to @oshun/logging or check tsconfig paths.
Issue 2: Database Connection Issues#
Error: schema "lilith" does not exist
Solution: Run schema creation:
CREATE SCHEMA IF NOT EXISTS lilith;
Issue 3: Event Type Mismatch#
Error: Unknown event type 'meditation:completed'
Solution: Update to new naming convention:
// Old
await redis.publish('meditation:completed', data);
// New
await eventBus.publish('lilith.meditation.completed', data);
Issue 4: Auth Token Invalid#
Error: Invalid audience in JWT
Solution: Ensure tokens include lilith in audience:
const payload = await verifyJWT(token, {
audience: ['lilith'], // Must include lilith
});
Issue 5: Missing Metrics/Tracing#
Warning: No OpenTelemetry exporter configured
Solution: Configure observability:
import { initTracing } from '@oshun/tracing';
import { initMetrics } from '@oshun/metrics';
await initTracing({
serviceName: 'lilith-meditation-core',
exporterUrl: process.env.OTEL_EXPORTER_URL,
});
await initMetrics({
serviceName: 'lilith-meditation-core',
port: 9090,
});
Migration Checklist#
Phase 1: Preparation#
- Clone Oshun monorepo
- Set up development environment
- Document all Lilith dependencies
- Identify custom code vs. replaceable utilities
Phase 2: Code Migration#
- Move apps to
apps/lilith/ - Move libs to
libs/lilith/ - Update import paths
- Replace utilities with
@oshun/*libraries
Phase 3: Database Migration#
- Update Prisma schema for multi-schema
- Create Lilith schema in Oshun cluster
- Migrate data
- Verify data integrity
Phase 4: Auth Migration#
- Integrate with unified auth
- Migrate user accounts
- Update mobile app auth flow
- Test token verification
Phase 5: Event System#
- Update event naming
- Migrate to
@oshun/event-bus - Add cross-domain event handlers
- Test event flow
Phase 6: Testing & Validation#
- Update test configuration
- Run all tests
- Test cross-domain integrations
- Performance testing
Phase 7: Deployment#
- Update CI/CD pipelines
- Configure Kubernetes namespaces
- Deploy to staging
- Production rollout
Support#
For migration assistance:
- Slack:
#oshun-migration - Documentation:
/docs/migration/ - Issues: GitHub Issues with
migrationlabel