/** * Unified schema file — all tables defined here. * drizzle-kit reads this file directly (avoids CJS/ESM extension issues). */ import { pgTable, text, timestamp, boolean, uuid, jsonb, index, uniqueIndex, real, integer, customType, } from 'drizzle-orm/pg-core'; // ─── Auth (BetterAuth-compatible) ──────────────────────────────────────────── export const users = pgTable('users', { id: text('id').primaryKey(), name: text('name').notNull(), email: text('email').notNull().unique(), emailVerified: boolean('email_verified').notNull().default(false), image: text('image'), role: text('role').notNull().default('member'), banned: boolean('banned').default(false), banReason: text('ban_reason'), banExpires: timestamp('ban_expires', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); export const sessions = pgTable( 'sessions', { id: text('id').primaryKey(), expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), token: text('token').notNull().unique(), ipAddress: text('ip_address'), userAgent: text('user_agent'), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ // Auth hot path: look up all sessions for a user (BetterAuth session list). index('sessions_user_id_idx').on(t.userId), // Session expiry cleanup queries. index('sessions_expires_at_idx').on(t.expiresAt), ], ); export const accounts = pgTable( 'accounts', { id: text('id').primaryKey(), accountId: text('account_id').notNull(), providerId: text('provider_id').notNull(), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), accessToken: text('access_token'), refreshToken: text('refresh_token'), idToken: text('id_token'), accessTokenExpiresAt: timestamp('access_token_expires_at', { withTimezone: true }), refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { withTimezone: true }), scope: text('scope'), password: text('password'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ // BetterAuth looks up accounts by (provider_id, account_id) on OAuth callback. index('accounts_provider_account_idx').on(t.providerId, t.accountId), // Also used in session validation to find linked accounts for a user. index('accounts_user_id_idx').on(t.userId), ], ); export const verifications = pgTable('verifications', { id: text('id').primaryKey(), identifier: text('identifier').notNull(), value: text('value').notNull(), expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); // ─── Teams ─────────────────────────────────────────────────────────────────── // Declared before projects because projects references teams. export const teams = pgTable('teams', { id: uuid('id').primaryKey().defaultRandom(), name: text('name').notNull(), slug: text('slug').notNull().unique(), ownerId: text('owner_id') .notNull() .references(() => users.id, { onDelete: 'restrict' }), managerId: text('manager_id') .notNull() .references(() => users.id, { onDelete: 'restrict' }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); export const teamMembers = pgTable( 'team_members', { id: uuid('id').primaryKey().defaultRandom(), teamId: uuid('team_id') .notNull() .references(() => teams.id, { onDelete: 'cascade' }), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), role: text('role', { enum: ['manager', 'member'] }) .notNull() .default('member'), invitedBy: text('invited_by').references(() => users.id, { onDelete: 'set null' }), joinedAt: timestamp('joined_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => ({ uniq: uniqueIndex('team_members_team_user_idx').on(t.teamId, t.userId), }), ); // ─── Brain ─────────────────────────────────────────────────────────────────── // Declared before Chat because conversations references projects. export const projects = pgTable('projects', { id: uuid('id').primaryKey().defaultRandom(), name: text('name').notNull(), description: text('description'), status: text('status', { enum: ['active', 'paused', 'completed', 'archived'] }) .notNull() .default('active'), ownerId: text('owner_id').references(() => users.id, { onDelete: 'set null' }), teamId: uuid('team_id').references(() => teams.id, { onDelete: 'cascade' }), ownerType: text('owner_type', { enum: ['user', 'team'] }) .notNull() .default('user'), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); export const missions = pgTable( 'missions', { id: uuid('id').primaryKey().defaultRandom(), name: text('name').notNull(), description: text('description'), status: text('status', { enum: ['planning', 'active', 'paused', 'completed', 'failed'] }) .notNull() .default('planning'), projectId: uuid('project_id').references(() => projects.id, { onDelete: 'set null' }), userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }), phase: text('phase'), milestones: jsonb('milestones').$type[]>(), config: jsonb('config'), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ index('missions_project_id_idx').on(t.projectId), index('missions_user_id_idx').on(t.userId), ], ); export const tasks = pgTable( 'tasks', { id: uuid('id').primaryKey().defaultRandom(), title: text('title').notNull(), description: text('description'), status: text('status', { enum: ['not-started', 'in-progress', 'blocked', 'done', 'cancelled'], }) .notNull() .default('not-started'), priority: text('priority', { enum: ['critical', 'high', 'medium', 'low'] }) .notNull() .default('medium'), projectId: uuid('project_id').references(() => projects.id, { onDelete: 'set null' }), missionId: uuid('mission_id').references(() => missions.id, { onDelete: 'set null' }), assignee: text('assignee'), tags: jsonb('tags').$type(), dueDate: timestamp('due_date', { withTimezone: true }), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ index('tasks_project_id_idx').on(t.projectId), index('tasks_mission_id_idx').on(t.missionId), index('tasks_status_idx').on(t.status), ], ); // ─── Coord Mission Tasks ───────────────────────────────────────────────────── // Join table tracking coord-managed tasks within a mission. // Scoped to userId for multi-tenant RBAC isolation. export const missionTasks = pgTable( 'mission_tasks', { id: uuid('id').primaryKey().defaultRandom(), missionId: uuid('mission_id') .notNull() .references(() => missions.id, { onDelete: 'cascade' }), taskId: uuid('task_id').references(() => tasks.id, { onDelete: 'set null' }), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), status: text('status', { enum: ['not-started', 'in-progress', 'blocked', 'done', 'cancelled'], }) .notNull() .default('not-started'), description: text('description'), notes: text('notes'), pr: text('pr'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ index('mission_tasks_mission_id_idx').on(t.missionId), index('mission_tasks_task_id_idx').on(t.taskId), index('mission_tasks_user_id_idx').on(t.userId), index('mission_tasks_status_idx').on(t.status), ], ); export const events = pgTable( 'events', { id: uuid('id').primaryKey().defaultRandom(), type: text('type').notNull(), title: text('title').notNull(), description: text('description'), date: timestamp('date', { withTimezone: true }).notNull().defaultNow(), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [index('events_type_idx').on(t.type), index('events_date_idx').on(t.date)], ); export const agents = pgTable( 'agents', { id: uuid('id').primaryKey().defaultRandom(), name: text('name').notNull(), provider: text('provider').notNull(), model: text('model').notNull(), status: text('status', { enum: ['idle', 'active', 'error', 'offline'] }) .notNull() .default('idle'), projectId: uuid('project_id').references(() => projects.id, { onDelete: 'set null' }), ownerId: text('owner_id').references(() => users.id, { onDelete: 'set null' }), systemPrompt: text('system_prompt'), allowedTools: jsonb('allowed_tools').$type(), skills: jsonb('skills').$type(), isSystem: boolean('is_system').notNull().default(false), config: jsonb('config'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ index('agents_project_id_idx').on(t.projectId), index('agents_owner_id_idx').on(t.ownerId), index('agents_is_system_idx').on(t.isSystem), ], ); export const tickets = pgTable( 'tickets', { id: uuid('id').primaryKey().defaultRandom(), title: text('title').notNull(), description: text('description'), status: text('status', { enum: ['open', 'in-progress', 'resolved', 'closed'] }) .notNull() .default('open'), priority: text('priority', { enum: ['critical', 'high', 'medium', 'low'] }) .notNull() .default('medium'), source: text('source'), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [index('tickets_status_idx').on(t.status)], ); export const appreciations = pgTable('appreciations', { id: uuid('id').primaryKey().defaultRandom(), fromUser: text('from_user'), toUser: text('to_user'), message: text('message').notNull(), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }); // ─── Chat ──────────────────────────────────────────────────────────────────── export const conversations = pgTable( 'conversations', { id: uuid('id').primaryKey().defaultRandom(), title: text('title'), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), projectId: uuid('project_id').references(() => projects.id, { onDelete: 'set null' }), agentId: uuid('agent_id').references(() => agents.id, { onDelete: 'set null' }), archived: boolean('archived').notNull().default(false), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ // Compound index for the most common query: conversations for a user filtered by archived. index('conversations_user_archived_idx').on(t.userId, t.archived), index('conversations_project_id_idx').on(t.projectId), index('conversations_agent_id_idx').on(t.agentId), ], ); export const messages = pgTable( 'messages', { id: uuid('id').primaryKey().defaultRandom(), conversationId: uuid('conversation_id') .notNull() .references(() => conversations.id, { onDelete: 'cascade' }), role: text('role', { enum: ['user', 'assistant', 'system'] }).notNull(), content: text('content').notNull(), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [index('messages_conversation_id_idx').on(t.conversationId)], ); // ─── pgvector custom type ─────────────────────────────────────────────────── const vector = customType<{ data: number[]; driverParam: string; config: { dimensions: number } }>({ dataType(config) { return `vector(${config?.dimensions ?? 1536})`; }, fromDriver(value: unknown): number[] { const str = value as string; return str .slice(1, -1) .split(',') .map((v) => Number(v)); }, toDriver(value: number[]): string { return `[${value.join(',')}]`; }, }); // ─── Memory ───────────────────────────────────────────────────────────────── export const preferences = pgTable( 'preferences', { id: uuid('id').primaryKey().defaultRandom(), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), key: text('key').notNull(), value: jsonb('value').notNull(), category: text('category', { enum: ['communication', 'coding', 'workflow', 'appearance', 'general'], }) .notNull() .default('general'), source: text('source'), mutable: boolean('mutable').notNull().default(true), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ index('preferences_user_id_idx').on(t.userId), // Unique constraint enables single-round-trip INSERT … ON CONFLICT DO UPDATE. uniqueIndex('preferences_user_key_idx').on(t.userId, t.key), ], ); export const insights = pgTable( 'insights', { id: uuid('id').primaryKey().defaultRandom(), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), content: text('content').notNull(), embedding: vector('embedding', { dimensions: 1536 }), source: text('source', { enum: ['agent', 'user', 'summarization', 'system'], }) .notNull() .default('agent'), category: text('category', { enum: ['decision', 'learning', 'preference', 'fact', 'pattern', 'general'], }) .notNull() .default('general'), relevanceScore: real('relevance_score').notNull().default(1.0), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), decayedAt: timestamp('decayed_at', { withTimezone: true }), }, (t) => [ index('insights_user_id_idx').on(t.userId), index('insights_category_idx').on(t.category), index('insights_relevance_idx').on(t.relevanceScore), ], ); // ─── Agent Logs ───────────────────────────────────────────────────────────── export const agentLogs = pgTable( 'agent_logs', { id: uuid('id').primaryKey().defaultRandom(), sessionId: text('session_id').notNull(), userId: text('user_id').references(() => users.id, { onDelete: 'set null' }), level: text('level', { enum: ['debug', 'info', 'warn', 'error'] }) .notNull() .default('info'), category: text('category', { enum: ['decision', 'tool_use', 'learning', 'error', 'general'], }) .notNull() .default('general'), content: text('content').notNull(), metadata: jsonb('metadata'), tier: text('tier', { enum: ['hot', 'warm', 'cold'] }) .notNull() .default('hot'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), summarizedAt: timestamp('summarized_at', { withTimezone: true }), archivedAt: timestamp('archived_at', { withTimezone: true }), }, (t) => [ // Compound index for session log queries (most common: session + tier filter). index('agent_logs_session_tier_idx').on(t.sessionId, t.tier), index('agent_logs_user_id_idx').on(t.userId), // Used by summarization cron to find hot logs older than a cutoff. index('agent_logs_tier_created_at_idx').on(t.tier, t.createdAt), ], ); // ─── Skills ───────────────────────────────────────────────────────────────── export const skills = pgTable( 'skills', { id: uuid('id').primaryKey().defaultRandom(), name: text('name').notNull().unique(), description: text('description'), version: text('version'), source: text('source', { enum: ['builtin', 'community', 'custom'] }) .notNull() .default('custom'), config: jsonb('config'), enabled: boolean('enabled').notNull().default(true), installedBy: text('installed_by').references(() => users.id, { onDelete: 'set null' }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [index('skills_enabled_idx').on(t.enabled)], ); // ─── Routing Rules ────────────────────────────────────────────────────────── export const routingRules = pgTable( 'routing_rules', { id: uuid('id').primaryKey().defaultRandom(), /** Human-readable rule name */ name: text('name').notNull(), /** Lower number = higher priority; unique per scope */ priority: integer('priority').notNull(), /** 'system' rules apply globally; 'user' rules are scoped to a specific user */ scope: text('scope', { enum: ['system', 'user'] }) .notNull() .default('system'), /** Null for system-scoped rules; FK to users.id for user-scoped rules */ userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }), /** Array of condition objects that must all match for the rule to fire */ conditions: jsonb('conditions').notNull().$type[]>(), /** Routing action to take when all conditions are satisfied */ action: jsonb('action').notNull().$type>(), /** Whether this rule is active */ enabled: boolean('enabled').notNull().default(true), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ // Lookup by scope + priority for ordered rule evaluation index('routing_rules_scope_priority_idx').on(t.scope, t.priority), // User-scoped rules lookup index('routing_rules_user_id_idx').on(t.userId), // Filter enabled rules efficiently index('routing_rules_enabled_idx').on(t.enabled), ], ); // ─── Provider Credentials ──────────────────────────────────────────────────── export const providerCredentials = pgTable( 'provider_credentials', { id: uuid('id').primaryKey().defaultRandom(), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), provider: text('provider').notNull(), credentialType: text('credential_type', { enum: ['api_key', 'oauth_token'] }).notNull(), encryptedValue: text('encrypted_value').notNull(), refreshToken: text('refresh_token'), expiresAt: timestamp('expires_at', { withTimezone: true }), metadata: jsonb('metadata'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ // Unique constraint: one credential entry per user per provider uniqueIndex('provider_credentials_user_provider_idx').on(t.userId, t.provider), index('provider_credentials_user_id_idx').on(t.userId), ], ); // ─── Summarization Jobs ───────────────────────────────────────────────────── export const summarizationJobs = pgTable( 'summarization_jobs', { id: uuid('id').primaryKey().defaultRandom(), status: text('status', { enum: ['pending', 'running', 'completed', 'failed'] }) .notNull() .default('pending'), logsProcessed: integer('logs_processed').notNull().default(0), insightsCreated: integer('insights_created').notNull().default(0), errorMessage: text('error_message'), startedAt: timestamp('started_at', { withTimezone: true }), completedAt: timestamp('completed_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [index('summarization_jobs_status_idx').on(t.status)], );