Disciplines · Migration

Lilith Migration Guide

1.

15sections4 minread

On this page

This guide helps developers migrate existing Lilith (consciousness/meditation platform) codebases to the unified Oshun monorepo.

Table of Contents#

  1. Overview
  2. Pre-Migration Checklist
  3. Architecture Changes
  4. Step-by-Step Migration
  5. Package Changes
  6. Database Migration
  7. API Changes
  8. Breaking Changes
  9. Event System
  10. Authentication
  11. Testing Updates
  12. 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:

bash
# 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#

text
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#

text
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#

bash
# 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:

typescript
// 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#

typescript
// 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#

typescript
// 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#

typescript
// 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#

typescript
// 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#

typescript
// 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#

typescript
// 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:

json
{
  "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#

json
{
  "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:

text
# Before
lilith-repo/prisma/schema.prisma

# After
oshun/libs/lilith/database/prisma/schema.prisma

Schema Changes#

prisma
// 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#

bash
# 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#

text
# 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#

typescript
// 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#

typescript
// 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#

typescript
// 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:

typescript
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:

typescript
// 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#

typescript
// 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#

typescript
// 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#

typescript
// 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#

typescript
// 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#

typescript
// 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#

text
Error: Cannot find module '@lilith/logging'

Solution: Update to @oshun/logging or check tsconfig paths.

Issue 2: Database Connection Issues#

text
Error: schema "lilith" does not exist

Solution: Run schema creation:

sql
CREATE SCHEMA IF NOT EXISTS lilith;

Issue 3: Event Type Mismatch#

text
Error: Unknown event type 'meditation:completed'

Solution: Update to new naming convention:

typescript
// Old
await redis.publish('meditation:completed', data);

// New
await eventBus.publish('lilith.meditation.completed', data);

Issue 4: Auth Token Invalid#

text
Error: Invalid audience in JWT

Solution: Ensure tokens include lilith in audience:

typescript
const payload = await verifyJWT(token, {
  audience: ['lilith'], // Must include lilith
});

Issue 5: Missing Metrics/Tracing#

text
Warning: No OpenTelemetry exporter configured

Solution: Configure observability:

typescript
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 migration label