This document tracks breaking changes introduced during the Oshun platform consolidation. Each change includes migration guidance and deprecation timelines.
Table of Contents#
- Version Compatibility
- API Breaking Changes
- SDK Breaking Changes
- Event Schema Changes
- Database Schema Changes
- Authentication Changes
- Configuration Changes
- Deprecation Schedule
Version Compatibility#
| Oshun Version | Lilith Compatibility | Yemaya Compatibility | Migration Required |
|---|---|---|---|
| 1.0.0 | Breaking | Breaking | Yes |
| 0.9.x | Partial (shim available) | Partial (shim available) | Recommended |
| < 0.9.0 | Not compatible | Not compatible | Required |
API Breaking Changes#
1. Authentication Header Changes#
Breaking in: Oshun 1.0.0
Before:
# Lilith
Authorization: Bearer {lilith_token}
X-Lilith-API-Key: {api_key}
# Yemaya
Authorization: Bearer {yemaya_token}
X-API-Key: {api_key}
After:
# All domains use unified format
Authorization: Bearer {oshun_jwt}
# OR
Authorization: ApiKey {oshun_api_key}
# OR
X-API-Key: {oshun_api_key}
Migration:
// Old
const headers = {
'X-Lilith-API-Key': process.env.LILITH_API_KEY,
};
// New
const headers = {
Authorization: `ApiKey ${process.env.OSHUN_API_KEY}`,
};
2. API Base URL Structure#
Breaking in: Oshun 1.0.0
Before:
# Lilith
https://api.lilith.io/v1/meditations
https://api.lilith.io/v1/users
# Yemaya
https://api.yemaya.studio/projects
https://api.yemaya.studio/assets
After:
# Unified gateway with domain prefixes
https://api.oshun.io/lilith/v1/meditations
https://api.oshun.io/lilith/v1/users
https://api.oshun.io/yemaya/v1/projects
https://api.oshun.io/yemaya/v1/assets
# New domains
https://api.oshun.io/isis/v1/generation
https://api.oshun.io/sophia/v1/search
https://api.oshun.io/hathor/v1/worlds
https://api.oshun.io/bellona/v1/exports
Migration:
// Old
const LILITH_API = 'https://api.lilith.io/v1';
const response = await fetch(`${LILITH_API}/meditations`);
// New
const OSHUN_API = 'https://api.oshun.io';
const response = await fetch(`${OSHUN_API}/lilith/v1/meditations`);
3. Response Envelope Format#
Breaking in: Oshun 1.0.0
Before (Lilith):
{
"success": true,
"data": { ... },
"error": null,
"meta": { "page": 1, "total": 100 }
}
Before (Yemaya):
{
"result": { ... },
"status": "ok",
"pagination": { ... }
}
After (All domains):
// Success response
{
"data": { ... },
"meta": {
"pagination": { "page": 1, "pageSize": 20, "total": 100 },
"requestId": "req_abc123"
}
}
// Error response
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Meditation not found",
"details": { "id": "med-123" },
"requestId": "req_abc123"
}
}
Migration:
// Old (Lilith)
const response = await fetch(url);
const { success, data, error } = await response.json();
if (!success) throw new Error(error);
// Old (Yemaya)
const response = await fetch(url);
const { result, status } = await response.json();
if (status !== 'ok') throw new Error('Request failed');
// New (All)
const response = await fetch(url);
const json = await response.json();
if (json.error) {
throw new ApiError(json.error.code, json.error.message);
}
return json.data;
4. Pagination Parameters#
Breaking in: Oshun 1.0.0
Before:
# Lilith
?page=1&limit=20
# Yemaya
?offset=0&count=20
After:
# Unified
?page=1&pageSize=20
# OR cursor-based
?cursor=abc123&pageSize=20
Response:
{
"data": [...],
"meta": {
"pagination": {
"page": 1,
"pageSize": 20,
"total": 100,
"totalPages": 5,
"hasNext": true,
"hasPrev": false,
"nextCursor": "eyJpZCI6MTAwfQ=="
}
}
}
5. Error Code Standardization#
Breaking in: Oshun 1.0.0
Before (Mixed formats):
// Lilith
{ "error": "MEDITATION_NOT_FOUND" }
{ "error": { "code": 404, "message": "Not found" } }
// Yemaya
{ "error": "project_not_found" }
{ "status": "error", "message": "Project not found" }
After (Standardized):
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "The requested resource was not found",
"details": {
"resourceType": "meditation",
"resourceId": "med-123"
}
}
}
Standard Error Codes:
| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR |
400 | Request validation failed |
INVALID_REQUEST |
400 | Malformed request |
UNAUTHORIZED |
401 | Authentication required |
INVALID_TOKEN |
401 | Token invalid or expired |
FORBIDDEN |
403 | Permission denied |
RESOURCE_NOT_FOUND |
404 | Resource doesn't exist |
CONFLICT |
409 | Resource conflict |
RATE_LIMITED |
429 | Rate limit exceeded |
INTERNAL_ERROR |
500 | Internal server error |
SERVICE_UNAVAILABLE |
503 | Service temporarily unavailable |
SDK Breaking Changes#
1. Client Instantiation#
Breaking in: SDK v2.0.0
Before:
// Lilith SDK
import { LilithClient } from '@lilith/sdk';
const client = new LilithClient({
apiKey: 'your-api-key',
environment: 'production',
});
// Yemaya SDK
import Yemaya from 'yemaya-sdk';
const client = Yemaya.init({
key: 'your-api-key',
});
After:
// All SDKs use factory functions
import { createLilithClient } from '@lilith/sdk';
import { createYemayaClient } from '@yemaya/sdk';
import { createIsisClient } from '@isis/client';
const lilith = createLilithClient({
baseUrl: 'https://api.oshun.io/lilith',
auth: {
type: 'api-key',
credentials: 'your-api-key',
},
});
const yemaya = createYemayaClient({
baseUrl: 'https://api.oshun.io/yemaya',
auth: {
type: 'api-key',
credentials: 'your-api-key',
},
});
2. Method Naming Conventions#
Breaking in: SDK v2.0.0
Before:
// Lilith - mixed conventions
await client.getMeditation(id);
await client.meditation.fetch(id);
await client.fetchMeditation(id);
// Yemaya - snake_case in some places
await client.get_project(id);
await client.projects.getById(id);
After:
// Consistent resource.action() pattern
await lilith.meditations.get(id);
await lilith.meditations.list({ page: 1 });
await lilith.meditations.create(data);
await lilith.meditations.update(id, data);
await lilith.meditations.delete(id);
await yemaya.projects.get(id);
await yemaya.projects.list();
await yemaya.projects.create(data);
3. Promise vs Callback API#
Breaking in: SDK v2.0.0
Before:
// Lilith supported callbacks
client.getMeditation(id, (err, meditation) => {
if (err) console.error(err);
else console.log(meditation);
});
After:
// Promises only (use async/await)
const meditation = await lilith.meditations.get(id);
// For error handling
try {
const meditation = await lilith.meditations.get(id);
} catch (error) {
if (error instanceof NotFoundError) {
// Handle not found
}
}
4. Type Definitions#
Breaking in: SDK v2.0.0
Before:
// Loose typing
interface Meditation {
id: string;
title: any;
data: object;
}
After:
// Strict Zod-validated types
import { z } from 'zod';
export const MeditationSchema = z.object({
id: z.string().uuid(),
title: z.string().min(1).max(200),
description: z.string().optional(),
duration: z.number().positive(),
audioUrl: z.string().url().optional(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
});
export type Meditation = z.infer<typeof MeditationSchema>;
Event Schema Changes#
1. Event Envelope Format#
Breaking in: Oshun 1.0.0
Before:
// Lilith events
{
"event": "meditation.completed",
"userId": "user-123",
"timestamp": 1704067200000
}
// Yemaya events
{
"type": "project_created",
"data": { "projectId": "proj-123" },
"time": "2024-01-15T10:30:00Z"
}
After:
{
"id": "evt_550e8400-e29b-41d4-a716-446655440000",
"type": "lilith.meditation.completed",
"source": "lilith",
"timestamp": "2024-01-15T10:30:00.000Z",
"correlationId": "req_abc123",
"payload": {
"meditationId": "med-123",
"userId": "user-456",
"duration": 600
},
"metadata": {
"environment": "production",
"version": "1.0.0"
}
}
2. Event Type Naming#
Breaking in: Oshun 1.0.0
| Old Event (Lilith) | New Event |
|---|---|
meditation.completed |
lilith.meditation.completed |
meditation.started |
lilith.meditation.started |
user.achievement |
lilith.achievement.earned |
session.created |
lilith.session.created |
| Old Event (Yemaya) | New Event |
|---|---|
project_created |
yemaya.project.created |
asset_uploaded |
yemaya.asset.uploaded |
export_complete |
bellona.export.completed |
generation_done |
isis.asset.generated |
3. Event Subscription API#
Breaking in: Oshun 1.0.0
Before:
// Lilith
redis.subscribe('meditation:completed', handler);
// Yemaya
bullMQ.on('completed', handler);
After:
import { EventBus } from '@oshun/event-bus';
const eventBus = new EventBus({
redis: { url: process.env.REDIS_URL },
consumerGroup: 'my-service',
});
// Pattern subscription
eventBus.subscribe('lilith.meditation.*', handler);
// Specific event
eventBus.subscribe('lilith.meditation.completed', handler);
await eventBus.start();
Database Schema Changes#
1. Multi-Schema Architecture#
Breaking in: Oshun 1.0.0
Before: Separate databases per domain
lilith-db → all tables
yemaya-db → all tables
After: Single cluster, multiple schemas
oshun-db
├── oshun_auth → users, sessions, permissions
├── lilith → meditations, progress, achievements
├── yemaya → projects, assets, teams
├── isis → generation_jobs, assets
├── sophia → documents, embeddings
├── hathor → worlds, characters, quests
└── bellona → builds, exports, artifacts
Migration:
// Before
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// After
datasource db {
provider = "postgresql"
url = env("LILITH_DATABASE_URL")
schemas = ["lilith"]
}
model Meditation {
id String @id
// ...
@@schema("lilith")
}
2. User ID References#
Breaking in: Oshun 1.0.0
Before:
model Meditation {
userId String // Direct user ID
user User @relation(...)
}
After:
model Meditation {
authUserId String // Reference to oshun_auth.users.id
lilithProfile LilithProfile @relation(fields: [authUserId], ...)
}
Migration Script:
-- Add new column
ALTER TABLE lilith.meditation ADD COLUMN auth_user_id UUID;
-- Migrate data
UPDATE lilith.meditation m
SET auth_user_id = (
SELECT auth_user_id FROM lilith.profile p WHERE p.id = m.user_id
);
-- Drop old column (after verification)
ALTER TABLE lilith.meditation DROP COLUMN user_id;
ALTER TABLE lilith.meditation RENAME COLUMN auth_user_id TO user_id;
3. Timestamp Format#
Breaking in: Oshun 1.0.0
Before:
model Entity {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // Optional soft delete
}
After:
model Entity {
createdAt DateTime @default(now()) @db.Timestamptz
updatedAt DateTime @updatedAt @db.Timestamptz
deletedAt DateTime? @db.Timestamptz
}
All timestamps now use TIMESTAMPTZ (timestamp with timezone) stored in UTC.
Authentication Changes#
1. JWT Claims Structure#
Breaking in: Oshun 1.0.0
Before (Lilith):
{
"sub": "user-123",
"email": "user@example.com",
"tier": "premium",
"exp": 1704067200
}
Before (Yemaya):
{
"userId": "user-123",
"organizationId": "org-456",
"role": "admin",
"exp": 1704067200
}
After (Unified):
{
"sub": "user-123",
"email": "user@example.com",
"iss": "https://auth.oshun.io",
"aud": ["lilith", "yemaya", "isis"],
"iat": 1704066300,
"exp": 1704067200,
"jti": "token-789",
"org": "org-456",
"permissions": ["read:meditations", "write:projects"],
"roles": ["user", "creator"]
}
2. API Key Format#
Breaking in: Oshun 1.0.0
Before:
# Lilith
lil_live_abc123...
lil_test_xyz789...
# Yemaya
ym_abc123...
After:
# Live keys
sk_live_oshun_abc123...
# Test keys
sk_test_oshun_xyz789...
# Restricted keys (limited scope)
rk_oshun_def456...
3. OAuth Scopes#
Breaking in: Oshun 1.0.0
Before:
# Lilith scopes
meditation:read
meditation:write
user:profile
# Yemaya scopes
projects
assets
export
After:
# Domain-prefixed scopes
lilith:meditation:read
lilith:meditation:write
lilith:user:read
yemaya:projects:read
yemaya:projects:write
yemaya:assets:read
yemaya:assets:write
isis:generation:read
isis:generation:write
# Wildcard scopes
lilith:* # All Lilith permissions
yemaya:*:read # Read all Yemaya resources
* # Full access (admin)
Configuration Changes#
1. Environment Variables#
Breaking in: Oshun 1.0.0
| Old Variable | New Variable | Notes |
|---|---|---|
LILITH_DATABASE_URL |
LILITH_DATABASE_URL |
Unchanged |
YEMAYA_DB_URL |
YEMAYA_DATABASE_URL |
Renamed |
REDIS_HOST, REDIS_PORT |
REDIS_URL |
Combined |
S3_BUCKET, S3_REGION |
STORAGE_URL |
S3-compatible URL |
OPENAI_API_KEY |
ISIS_OPENAI_API_KEY |
Domain-prefixed |
JWT_SECRET |
AUTH_JWT_PUBLIC_KEY |
RSA instead of symmetric |
Full Environment Template:
# Core
NODE_ENV=production
LOG_LEVEL=info
# Databases (per domain)
LILITH_DATABASE_URL=postgresql://...?schema=lilith
YEMAYA_DATABASE_URL=postgresql://...?schema=yemaya
ISIS_DATABASE_URL=postgresql://...?schema=isis
# ... etc
# Shared Infrastructure
REDIS_URL=redis://localhost:6379
STORAGE_URL=s3://oshun-assets.s3.us-east-1.amazonaws.com
# Authentication
AUTH_JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----..."
AUTH_ISSUER=https://auth.oshun.io
# AI Providers (managed by Isis)
ISIS_OPENAI_API_KEY=sk-...
ISIS_ANTHROPIC_API_KEY=sk-ant-...
ISIS_REPLICATE_API_TOKEN=r8_...
# Observability
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.oshun.io
SENTRY_DSN=https://...@sentry.io/...
2. Configuration File Format#
Breaking in: Oshun 1.0.0
Before: Mixed formats
# lilith-config.yaml
database:
host: localhost
port: 5432
# yemaya.config.json
{
"storage": {
"bucket": "yemaya-assets"
}
}
After: Unified config structure
// config/lilith.config.ts
import { defineConfig } from '@oshun/config';
export default defineConfig({
domain: 'lilith',
database: {
url: process.env.LILITH_DATABASE_URL,
schema: 'lilith',
},
redis: {
url: process.env.REDIS_URL,
},
// Type-safe configuration
});
Deprecation Schedule#
Immediate (Oshun 1.0.0)#
| Feature | Status | Removal |
|---|---|---|
| Lilith standalone API | Deprecated | v2.0.0 |
| Yemaya standalone API | Deprecated | v2.0.0 |
| Legacy event formats | Deprecated | v1.5.0 |
| Callback-based SDK methods | Removed | N/A |
Oshun 1.5.0 (Q2 2024)#
| Feature | Status | Removal |
|---|---|---|
| Legacy event formats | Removed | N/A |
| Old API response format shim | Deprecated | v2.0.0 |
| Symmetric JWT validation | Deprecated | v2.0.0 |
Oshun 2.0.0 (Q4 2024)#
| Feature | Status | Removal |
|---|---|---|
| Standalone domain APIs | Removed | N/A |
| Old API response format shim | Removed | N/A |
| Non-schema database connections | Removed | N/A |
| Legacy API key formats | Removed | N/A |
Compatibility Shims#
For gradual migration, compatibility shims are available:
Response Format Shim#
import { legacyResponseMiddleware } from '@oshun/compat';
// Converts new format to legacy format for old clients
app.use(
'/api/v1/*',
legacyResponseMiddleware({
format: 'lilith', // or 'yemaya'
})
);
Event Bridge#
import { EventBridge } from '@oshun/compat';
// Translates old event names to new format
const bridge = new EventBridge({
mappings: {
'meditation:completed': 'lilith.meditation.completed',
project_created: 'yemaya.project.created',
},
});
await bridge.start();
API Key Translation#
import { translateApiKey } from '@oshun/compat';
// Converts old format keys to new format
const newKey = translateApiKey('lil_live_abc123');
// Returns: sk_live_oshun_abc123
Getting Help#
- Migration Support:
#oshun-migrationon Slack - Breaking Changes Questions: Create GitHub Issue with
breaking-changelabel - Compatibility Shims: See
/libs/compat/for source code