Files
stack/apps/api/prisma/schema.prisma
Jason Woltje d49a978252
All checks were successful
ci/woodpecker/push/api Pipeline was successful
fix(api): add debian-openssl-3.0.x to Prisma binaryTargets
Prisma client was generated for debian-openssl-1.1.x but the
node:24-slim Docker image runs Debian Bookworm with OpenSSL 3.0.x.
Adding explicit binaryTargets ensures the correct query engine is
always generated regardless of build host.

Also removes inline comments from .env.example rate limit values —
Portainer treats trailing comments as part of the value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 20:43:51 -06:00

1537 lines
48 KiB
Plaintext

// Mosaic Stack Database Schema
// PostgreSQL 17 with pgvector extension
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "debian-openssl-3.0.x"]
previewFeatures = ["postgresqlExtensions"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
extensions = [pgvector(map: "vector"), uuid_ossp(map: "uuid-ossp")]
}
// ============================================
// ENUMS
// ============================================
enum TaskStatus {
NOT_STARTED
IN_PROGRESS
PAUSED
COMPLETED
ARCHIVED
}
enum TaskPriority {
LOW
MEDIUM
HIGH
}
enum ProjectStatus {
PLANNING
ACTIVE
PAUSED
COMPLETED
ARCHIVED
}
enum WorkspaceMemberRole {
OWNER
ADMIN
MEMBER
GUEST
}
enum TeamMemberRole {
OWNER
ADMIN
MEMBER
}
enum ActivityAction {
CREATED
UPDATED
DELETED
COMPLETED
ASSIGNED
COMMENTED
LOGIN
LOGOUT
PASSWORD_RESET
EMAIL_VERIFIED
CREDENTIAL_CREATED
CREDENTIAL_ACCESSED
CREDENTIAL_ROTATED
CREDENTIAL_REVOKED
}
enum EntityType {
TASK
EVENT
PROJECT
WORKSPACE
USER
IDEA
DOMAIN
CREDENTIAL
}
enum IdeaStatus {
CAPTURED
PROCESSING
ACTIONABLE
ARCHIVED
DISCARDED
}
enum RelationshipType {
BLOCKS
BLOCKED_BY
DEPENDS_ON
PARENT_OF
CHILD_OF
RELATED_TO
DUPLICATE_OF
SUPERSEDES
PART_OF
}
enum AgentStatus {
IDLE
WORKING
WAITING
ERROR
TERMINATED
}
enum AgentTaskStatus {
PENDING
RUNNING
COMPLETED
FAILED
}
enum AgentTaskPriority {
LOW
MEDIUM
HIGH
}
enum EntryStatus {
DRAFT
PUBLISHED
ARCHIVED
}
enum Visibility {
PRIVATE
WORKSPACE
PUBLIC
}
enum FormalityLevel {
VERY_CASUAL
CASUAL
NEUTRAL
FORMAL
VERY_FORMAL
}
enum RunnerJobStatus {
PENDING
QUEUED
RUNNING
COMPLETED
FAILED
CANCELLED
}
enum JobStepPhase {
SETUP
EXECUTION
VALIDATION
CLEANUP
}
enum JobStepType {
COMMAND
AI_ACTION
GATE
ARTIFACT
}
enum JobStepStatus {
PENDING
RUNNING
COMPLETED
FAILED
SKIPPED
}
enum FederationConnectionStatus {
PENDING
ACTIVE
SUSPENDED
DISCONNECTED
}
enum FederationMessageType {
QUERY
COMMAND
EVENT
}
enum FederationMessageStatus {
PENDING
DELIVERED
FAILED
TIMEOUT
}
enum CredentialType {
API_KEY
OAUTH_TOKEN
ACCESS_TOKEN
SECRET
PASSWORD
CUSTOM
}
enum CredentialScope {
USER
WORKSPACE
SYSTEM
}
enum TerminalSessionStatus {
ACTIVE
CLOSED
}
// ============================================
// MODELS
// ============================================
model User {
id String @id @default(uuid()) @db.Uuid
email String @unique
name String
emailVerified Boolean @default(false) @map("email_verified")
image String?
authProviderId String? @unique @map("auth_provider_id")
preferences Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
ownedWorkspaces Workspace[] @relation("WorkspaceOwner")
workspaceMemberships WorkspaceMember[]
teamMemberships TeamMember[]
assignedTasks Task[] @relation("TaskAssignee")
createdTasks Task[] @relation("TaskCreator")
createdEvents Event[] @relation("EventCreator")
createdProjects Project[] @relation("ProjectCreator")
activityLogs ActivityLog[]
sessions Session[]
accounts Account[]
ideas Idea[] @relation("IdeaCreator")
relationships Relationship[] @relation("RelationshipCreator")
agentSessions AgentSession[]
agentTasks AgentTask[] @relation("AgentTaskCreator")
userLayouts UserLayout[]
userPreference UserPreference?
knowledgeEntryVersions KnowledgeEntryVersion[] @relation("EntryVersionAuthor")
llmProviders LlmProviderInstance[] @relation("UserLlmProviders")
federatedIdentities FederatedIdentity[]
llmUsageLogs LlmUsageLog[] @relation("UserLlmUsageLogs")
userCredentials UserCredential[] @relation("UserCredentials")
@@map("users")
}
model UserPreference {
id String @id @default(uuid()) @db.Uuid
userId String @unique @map("user_id") @db.Uuid
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
theme String @default("system")
locale String @default("en")
timezone String?
settings Json @default("{}")
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@map("user_preferences")
}
model Workspace {
id String @id @default(uuid()) @db.Uuid
name String
ownerId String @map("owner_id") @db.Uuid
settings Json @default("{}")
matrixRoomId String? @map("matrix_room_id")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
owner User @relation("WorkspaceOwner", fields: [ownerId], references: [id], onDelete: Cascade)
members WorkspaceMember[]
teams Team[]
tasks Task[]
events Event[]
projects Project[]
activityLogs ActivityLog[]
memoryEmbeddings MemoryEmbedding[]
domains Domain[]
ideas Idea[]
relationships Relationship[]
agents Agent[]
agentSessions AgentSession[]
agentTasks AgentTask[]
userLayouts UserLayout[]
knowledgeEntries KnowledgeEntry[]
knowledgeTags KnowledgeTag[]
cronSchedules CronSchedule[]
personalities Personality[]
llmSettings WorkspaceLlmSettings?
qualityGates QualityGate[]
runnerJobs RunnerJob[]
federationConnections FederationConnection[]
federationMessages FederationMessage[]
federationEventSubscriptions FederationEventSubscription[]
llmUsageLogs LlmUsageLog[]
userCredentials UserCredential[]
terminalSessions TerminalSession[]
@@index([ownerId])
@@map("workspaces")
}
model WorkspaceMember {
workspaceId String @map("workspace_id") @db.Uuid
userId String @map("user_id") @db.Uuid
role WorkspaceMemberRole @default(MEMBER)
joinedAt DateTime @default(now()) @map("joined_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([workspaceId, userId])
@@index([userId])
@@map("workspace_members")
}
model Team {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
name String
description String? @db.Text
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
members TeamMember[]
@@index([workspaceId])
@@map("teams")
}
model TeamMember {
teamId String @map("team_id") @db.Uuid
userId String @map("user_id") @db.Uuid
role TeamMemberRole @default(MEMBER)
joinedAt DateTime @default(now()) @map("joined_at") @db.Timestamptz
// Relations
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([teamId, userId])
@@index([userId])
@@map("team_members")
}
model Task {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
title String
description String? @db.Text
status TaskStatus @default(NOT_STARTED)
priority TaskPriority @default(MEDIUM)
dueDate DateTime? @map("due_date") @db.Timestamptz
assigneeId String? @map("assignee_id") @db.Uuid
creatorId String @map("creator_id") @db.Uuid
projectId String? @map("project_id") @db.Uuid
parentId String? @map("parent_id") @db.Uuid
domainId String? @map("domain_id") @db.Uuid
sortOrder Int @default(0) @map("sort_order")
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
completedAt DateTime? @map("completed_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
assignee User? @relation("TaskAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
creator User @relation("TaskCreator", fields: [creatorId], references: [id], onDelete: Cascade)
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
parent Task? @relation("TaskSubtasks", fields: [parentId], references: [id], onDelete: Cascade)
subtasks Task[] @relation("TaskSubtasks")
domain Domain? @relation(fields: [domainId], references: [id], onDelete: SetNull)
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, status])
@@index([workspaceId, dueDate])
@@index([assigneeId])
@@index([projectId])
@@index([parentId])
@@index([domainId])
@@map("tasks")
}
model Event {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
title String
description String? @db.Text
startTime DateTime @map("start_time") @db.Timestamptz
endTime DateTime? @map("end_time") @db.Timestamptz
allDay Boolean @default(false) @map("all_day")
location String?
recurrence Json?
creatorId String @map("creator_id") @db.Uuid
projectId String? @map("project_id") @db.Uuid
domainId String? @map("domain_id") @db.Uuid
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
creator User @relation("EventCreator", fields: [creatorId], references: [id], onDelete: Cascade)
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
domain Domain? @relation(fields: [domainId], references: [id], onDelete: SetNull)
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, startTime])
@@index([creatorId])
@@index([projectId])
@@index([domainId])
@@map("events")
}
model Project {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
name String
description String? @db.Text
status ProjectStatus @default(PLANNING)
startDate DateTime? @map("start_date") @db.Date
endDate DateTime? @map("end_date") @db.Date
creatorId String @map("creator_id") @db.Uuid
domainId String? @map("domain_id") @db.Uuid
color String?
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
creator User @relation("ProjectCreator", fields: [creatorId], references: [id], onDelete: Cascade)
tasks Task[]
events Event[]
domain Domain? @relation(fields: [domainId], references: [id], onDelete: SetNull)
ideas Idea[]
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, status])
@@index([creatorId])
@@index([domainId])
@@map("projects")
}
model ActivityLog {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
userId String @map("user_id") @db.Uuid
action ActivityAction
entityType EntityType @map("entity_type")
entityId String @map("entity_id") @db.Uuid
details Json @default("{}")
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, createdAt])
@@index([entityType, entityId])
@@index([userId])
@@index([action])
@@map("activity_logs")
}
model MemoryEmbedding {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
content String @db.Text
// Note: vector dimension (1536) must match EMBEDDING_DIMENSION constant in @mosaic/shared
embedding Unsupported("vector(1536)")?
entityType EntityType? @map("entity_type")
entityId String? @map("entity_id") @db.Uuid
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@index([workspaceId])
@@map("memory_embeddings")
}
// ============================================
// NEW MODELS
// ============================================
model Domain {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
name String
slug String
description String? @db.Text
color String?
icon String?
sortOrder Int @default(0) @map("sort_order")
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
tasks Task[]
events Event[]
projects Project[]
ideas Idea[]
@@unique([id, workspaceId])
@@unique([workspaceId, slug])
@@index([workspaceId])
@@map("domains")
}
model Idea {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
domainId String? @map("domain_id") @db.Uuid
projectId String? @map("project_id") @db.Uuid
// Core fields
title String?
content String @db.Text
// Status
status IdeaStatus @default(CAPTURED)
priority TaskPriority @default(MEDIUM)
// Categorization
category String?
tags String[]
metadata Json @default("{}")
// Embedding for semantic search (pgvector)
embedding Unsupported("vector(1536)")?
// Audit
creatorId String @map("creator_id") @db.Uuid
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
domain Domain? @relation(fields: [domainId], references: [id], onDelete: SetNull)
project Project? @relation(fields: [projectId], references: [id], onDelete: SetNull)
creator User @relation("IdeaCreator", fields: [creatorId], references: [id], onDelete: Cascade)
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, status])
@@index([domainId])
@@index([projectId])
@@index([creatorId])
@@map("ideas")
}
model Relationship {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
// Source entity
sourceType EntityType @map("source_type")
sourceId String @map("source_id") @db.Uuid
// Target entity
targetType EntityType @map("target_type")
targetId String @map("target_id") @db.Uuid
// Relationship type
relationship RelationshipType
metadata Json @default("{}")
notes String? @db.Text
// Audit
creatorId String @map("creator_id") @db.Uuid
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
creator User @relation("RelationshipCreator", fields: [creatorId], references: [id], onDelete: Cascade)
// Prevent duplicate relationships
@@unique([workspaceId, sourceType, sourceId, targetType, targetId, relationship])
@@index([sourceType, sourceId])
@@index([targetType, targetId])
@@index([relationship])
@@map("relationships")
}
model Agent {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
// Identity
agentId String @map("agent_id")
name String?
model String?
role String?
// Status
status AgentStatus @default(IDLE)
currentTask String? @map("current_task") @db.Text
// Performance metrics
metrics Json @default("{\"totalTasks\": 0, \"successfulTasks\": 0, \"failedTasks\": 0, \"avgResponseTimeMs\": 0}")
// Health
lastHeartbeat DateTime? @map("last_heartbeat") @db.Timestamptz
errorCount Int @default(0) @map("error_count")
lastError String? @map("last_error") @db.Text
// Firing history
firedCount Int @default(0) @map("fired_count")
fireHistory Json @default("[]") @map("fire_history")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
terminatedAt DateTime? @map("terminated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
sessions AgentSession[]
@@unique([workspaceId, agentId])
@@index([workspaceId])
@@index([status])
@@map("agents")
}
model AgentTask {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
// Task details
title String
description String? @db.Text
status AgentTaskStatus @default(PENDING)
priority AgentTaskPriority @default(MEDIUM)
// Agent configuration
agentType String @map("agent_type")
agentConfig Json @default("{}") @map("agent_config")
// Results
result Json?
error String? @db.Text
// Timing
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
startedAt DateTime? @map("started_at") @db.Timestamptz
completedAt DateTime? @map("completed_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
createdBy User @relation("AgentTaskCreator", fields: [createdById], references: [id], onDelete: Cascade)
createdById String @map("created_by_id") @db.Uuid
runnerJobs RunnerJob[]
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, status])
@@index([createdById])
@@index([agentType])
@@map("agent_tasks")
}
model AgentSession {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
userId String @map("user_id") @db.Uuid
agentId String? @map("agent_id") @db.Uuid
// Identity
sessionKey String @map("session_key")
label String?
channel String?
// Context
contextSummary String? @map("context_summary") @db.Text
messageCount Int @default(0) @map("message_count")
// Status
isActive Boolean @default(true) @map("is_active")
startedAt DateTime @default(now()) @map("started_at") @db.Timestamptz
lastMessageAt DateTime? @map("last_message_at") @db.Timestamptz
endedAt DateTime? @map("ended_at") @db.Timestamptz
metadata Json @default("{}")
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
agent Agent? @relation(fields: [agentId], references: [id], onDelete: SetNull)
@@unique([workspaceId, sessionKey])
@@index([workspaceId])
@@index([userId])
@@index([agentId])
@@index([isActive])
@@map("agent_sessions")
}
model WidgetDefinition {
id String @id @default(uuid()) @db.Uuid
name String @unique
displayName String @map("display_name")
description String? @db.Text
component String
// Default size (grid units)
defaultWidth Int @default(1) @map("default_width")
defaultHeight Int @default(1) @map("default_height")
minWidth Int @default(1) @map("min_width")
minHeight Int @default(1) @map("min_height")
maxWidth Int? @map("max_width")
maxHeight Int? @map("max_height")
// Configuration schema (JSON Schema for widget config)
configSchema Json @default("{}") @map("config_schema")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
@@map("widget_definitions")
}
model UserLayout {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
userId String @map("user_id") @db.Uuid
name String
isDefault Boolean @default(false) @map("is_default")
// Layout configuration (array of widget placements)
layout Json @default("[]")
// Additional metadata for the layout
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([id, workspaceId])
@@unique([workspaceId, userId, name])
@@index([userId])
@@map("user_layouts")
}
// ============================================
// AUTHENTICATION MODELS (BetterAuth)
// ============================================
model Session {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
token String @unique
expiresAt DateTime @map("expires_at") @db.Timestamptz
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([token])
@@map("sessions")
}
model Account {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
accountId String @map("account_id")
providerId String @map("provider_id")
accessToken String? @map("access_token")
refreshToken String? @map("refresh_token")
idToken String? @map("id_token")
accessTokenExpiresAt DateTime? @map("access_token_expires_at") @db.Timestamptz
refreshTokenExpiresAt DateTime? @map("refresh_token_expires_at") @db.Timestamptz
scope String?
password String?
encryptionVersion String? @map("encryption_version") @db.VarChar(20)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([providerId, accountId])
@@index([userId])
@@index([encryptionVersion])
@@map("accounts")
}
model Verification {
id String @id @default(uuid()) @db.Uuid
identifier String
value String
expiresAt DateTime @map("expires_at") @db.Timestamptz
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@index([identifier])
@@map("verifications")
}
// ============================================
// USER CREDENTIALS MODULE
// ============================================
model UserCredential {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
workspaceId String? @map("workspace_id") @db.Uuid
// Identity
name String
provider String // "github", "openai", "custom"
type CredentialType
scope CredentialScope @default(USER)
// Encrypted storage
encryptedValue String @map("encrypted_value") @db.Text
maskedValue String? @map("masked_value") @db.VarChar(20)
// Metadata
description String? @db.Text
expiresAt DateTime? @map("expires_at") @db.Timestamptz
lastUsedAt DateTime? @map("last_used_at") @db.Timestamptz
metadata Json @default("{}")
// Status
isActive Boolean @default(true) @map("is_active")
rotatedAt DateTime? @map("rotated_at") @db.Timestamptz
// Audit
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
user User @relation("UserCredentials", fields: [userId], references: [id], onDelete: Cascade)
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@unique([userId, workspaceId, provider, name])
@@index([userId])
@@index([workspaceId])
@@index([userId, scope])
@@index([workspaceId, scope])
@@index([scope, isActive])
@@map("user_credentials")
}
// ============================================
// KNOWLEDGE MODULE
// ============================================
model KnowledgeEntry {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
// Identity
slug String
title String
// Content
content String @db.Text
contentHtml String? @map("content_html") @db.Text
summary String?
// Full-text search vector (automatically maintained by trigger)
searchVector Unsupported("tsvector")? @map("search_vector")
// Status
status EntryStatus @default(DRAFT)
visibility Visibility @default(PRIVATE)
// Audit
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
createdBy String @map("created_by") @db.Uuid
updatedBy String @map("updated_by") @db.Uuid
// Relations
tags KnowledgeEntryTag[]
outgoingLinks KnowledgeLink[] @relation("SourceEntry")
incomingLinks KnowledgeLink[] @relation("TargetEntry")
versions KnowledgeEntryVersion[]
embedding KnowledgeEmbedding?
@@unique([workspaceId, slug])
@@index([workspaceId, status])
@@index([workspaceId, updatedAt])
@@index([createdBy])
@@index([updatedBy])
// Note: GIN index on searchVector created via migration (not supported in Prisma schema)
@@map("knowledge_entries")
}
model KnowledgeEntryVersion {
id String @id @default(uuid()) @db.Uuid
entryId String @map("entry_id") @db.Uuid
entry KnowledgeEntry @relation(fields: [entryId], references: [id], onDelete: Cascade)
version Int
title String
content String @db.Text
summary String?
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
createdBy String @map("created_by") @db.Uuid
author User @relation("EntryVersionAuthor", fields: [createdBy], references: [id])
changeNote String? @map("change_note")
@@unique([entryId, version])
@@index([entryId, version])
@@map("knowledge_entry_versions")
}
model KnowledgeLink {
id String @id @default(uuid()) @db.Uuid
sourceId String @map("source_id") @db.Uuid
source KnowledgeEntry @relation("SourceEntry", fields: [sourceId], references: [id], onDelete: Cascade)
targetId String @map("target_id") @db.Uuid
target KnowledgeEntry @relation("TargetEntry", fields: [targetId], references: [id], onDelete: Cascade)
// Link metadata
linkText String @map("link_text")
displayText String @map("display_text")
context String?
// Position in source content
positionStart Int @map("position_start")
positionEnd Int @map("position_end")
// Resolution status
resolved Boolean @default(true)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
@@unique([sourceId, targetId])
@@index([sourceId])
@@index([targetId])
@@index([resolved])
@@map("knowledge_links")
}
model KnowledgeTag {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
name String
slug String
color String?
description String?
entries KnowledgeEntryTag[]
@@unique([workspaceId, slug])
@@index([workspaceId])
@@map("knowledge_tags")
}
model KnowledgeEntryTag {
entryId String @map("entry_id") @db.Uuid
entry KnowledgeEntry @relation(fields: [entryId], references: [id], onDelete: Cascade)
tagId String @map("tag_id") @db.Uuid
tag KnowledgeTag @relation(fields: [tagId], references: [id], onDelete: Cascade)
@@id([entryId, tagId])
@@index([entryId])
@@index([tagId])
@@map("knowledge_entry_tags")
}
model KnowledgeEmbedding {
id String @id @default(uuid()) @db.Uuid
entryId String @unique @map("entry_id") @db.Uuid
entry KnowledgeEntry @relation(fields: [entryId], references: [id], onDelete: Cascade)
embedding Unsupported("vector(1536)")
model String
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@index([entryId])
@@map("knowledge_embeddings")
}
// ============================================
// CRON JOBS
// ============================================
model CronSchedule {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
// Cron configuration
expression String // Standard cron: "0 9 * * *" = 9am daily
command String // MoltBot command to trigger
// State
enabled Boolean @default(true)
lastRun DateTime? @map("last_run") @db.Timestamptz
nextRun DateTime? @map("next_run") @db.Timestamptz
// Audit
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@index([workspaceId])
@@index([workspaceId, enabled])
@@index([nextRun])
@@map("cron_schedules")
}
// ============================================
// PERSONALITY MODULE
// ============================================
model Personality {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
// Identity
name String // unique identifier slug
displayName String @map("display_name")
description String? @db.Text
// System prompt
systemPrompt String @map("system_prompt") @db.Text
// LLM configuration
temperature Float? // null = use provider default
maxTokens Int? @map("max_tokens") // null = use provider default
llmProviderInstanceId String? @map("llm_provider_instance_id") @db.Uuid
// Status
isDefault Boolean @default(false) @map("is_default")
isEnabled Boolean @default(true) @map("is_enabled")
// Audit
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
llmProviderInstance LlmProviderInstance? @relation("PersonalityLlmProvider", fields: [llmProviderInstanceId], references: [id], onDelete: SetNull)
workspaceLlmSettings WorkspaceLlmSettings[] @relation("WorkspacePersonality")
@@unique([id, workspaceId])
@@unique([workspaceId, name])
@@index([workspaceId])
@@index([workspaceId, isDefault])
@@index([workspaceId, isEnabled])
@@index([llmProviderInstanceId])
@@map("personalities")
}
// ============================================
// LLM PROVIDER MODULE
// ============================================
model LlmProviderInstance {
id String @id @default(uuid()) @db.Uuid
providerType String @map("provider_type") // "ollama" | "claude" | "openai"
displayName String @map("display_name")
userId String? @map("user_id") @db.Uuid // NULL = system-level, UUID = user-level
config Json // Provider-specific configuration
isDefault Boolean @default(false) @map("is_default")
isEnabled Boolean @default(true) @map("is_enabled")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
user User? @relation("UserLlmProviders", fields: [userId], references: [id], onDelete: Cascade)
personalities Personality[] @relation("PersonalityLlmProvider")
workspaceLlmSettings WorkspaceLlmSettings[] @relation("WorkspaceLlmProvider")
llmUsageLogs LlmUsageLog[] @relation("LlmUsageLogs")
@@index([userId])
@@index([providerType])
@@index([isDefault])
@@index([isEnabled])
@@map("llm_provider_instances")
}
// ============================================
// WORKSPACE LLM SETTINGS
// ============================================
model WorkspaceLlmSettings {
id String @id @default(uuid()) @db.Uuid
workspaceId String @unique @map("workspace_id") @db.Uuid
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
defaultLlmProviderId String? @map("default_llm_provider_id") @db.Uuid
defaultLlmProvider LlmProviderInstance? @relation("WorkspaceLlmProvider", fields: [defaultLlmProviderId], references: [id], onDelete: SetNull)
defaultPersonalityId String? @map("default_personality_id") @db.Uuid
defaultPersonality Personality? @relation("WorkspacePersonality", fields: [defaultPersonalityId], references: [id], onDelete: SetNull)
settings Json? @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@index([workspaceId])
@@index([defaultLlmProviderId])
@@index([defaultPersonalityId])
@@map("workspace_llm_settings")
}
// ============================================
// QUALITY GATE MODULE
// ============================================
model QualityGate {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
name String
description String?
type String // 'build' | 'lint' | 'test' | 'coverage' | 'custom'
command String?
expectedOutput String? @map("expected_output")
isRegex Boolean @default(false) @map("is_regex")
required Boolean @default(true)
order Int @default(0)
isEnabled Boolean @default(true) @map("is_enabled")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@unique([workspaceId, name])
@@index([workspaceId])
@@index([workspaceId, isEnabled])
@@map("quality_gates")
}
model TaskRejection {
id String @id @default(uuid()) @db.Uuid
taskId String @map("task_id")
workspaceId String @map("workspace_id")
agentId String @map("agent_id")
attemptCount Int @map("attempt_count")
failures Json // FailureSummary[]
originalTask String @map("original_task")
startedAt DateTime @map("started_at") @db.Timestamptz
rejectedAt DateTime @map("rejected_at") @db.Timestamptz
escalated Boolean @default(false)
manualReview Boolean @default(false) @map("manual_review")
resolvedAt DateTime? @map("resolved_at") @db.Timestamptz
resolution String?
@@index([taskId])
@@index([workspaceId])
@@index([agentId])
@@index([escalated])
@@index([manualReview])
@@map("task_rejections")
}
model TokenBudget {
id String @id @default(uuid()) @db.Uuid
taskId String @unique @map("task_id") @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
agentId String @map("agent_id")
// Budget allocation
allocatedTokens Int @map("allocated_tokens")
estimatedComplexity String @map("estimated_complexity") // "low", "medium", "high", "critical"
// Usage tracking
inputTokensUsed Int @default(0) @map("input_tokens_used")
outputTokensUsed Int @default(0) @map("output_tokens_used")
totalTokensUsed Int @default(0) @map("total_tokens_used")
// Cost tracking
estimatedCost Decimal? @map("estimated_cost") @db.Decimal(10, 6)
// State
startedAt DateTime @default(now()) @map("started_at") @db.Timestamptz
lastUpdatedAt DateTime @updatedAt @map("last_updated_at") @db.Timestamptz
completedAt DateTime? @map("completed_at") @db.Timestamptz
// Analysis
budgetUtilization Float? @map("budget_utilization") // 0.0 - 1.0
suspiciousPattern Boolean @default(false) @map("suspicious_pattern")
suspiciousReason String? @map("suspicious_reason")
@@index([taskId])
@@index([workspaceId])
@@index([suspiciousPattern])
@@map("token_budgets")
}
// ============================================
// RUNNER JOB TRACKING MODULE
// ============================================
model RunnerJob {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
agentTaskId String? @map("agent_task_id") @db.Uuid
// Job details
type String // 'git-status', 'code-task', 'priority-calc'
status RunnerJobStatus @default(PENDING)
priority Int
progressPercent Int @default(0) @map("progress_percent")
version Int @default(1) // Optimistic locking version
// Results
result Json?
error String? @db.Text
// Timing
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
startedAt DateTime? @map("started_at") @db.Timestamptz
completedAt DateTime? @map("completed_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
agentTask AgentTask? @relation(fields: [agentTaskId], references: [id], onDelete: SetNull)
steps JobStep[]
events JobEvent[]
@@unique([id, workspaceId])
@@index([workspaceId])
@@index([workspaceId, status])
@@index([agentTaskId])
@@index([priority])
@@map("runner_jobs")
}
model JobStep {
id String @id @default(uuid()) @db.Uuid
jobId String @map("job_id") @db.Uuid
// Step details
ordinal Int
phase JobStepPhase
name String
type JobStepType
status JobStepStatus @default(PENDING)
// Output and metrics
output String? @db.Text
tokensInput Int? @map("tokens_input")
tokensOutput Int? @map("tokens_output")
// Timing
startedAt DateTime? @map("started_at") @db.Timestamptz
completedAt DateTime? @map("completed_at") @db.Timestamptz
durationMs Int? @map("duration_ms")
// Relations
job RunnerJob @relation(fields: [jobId], references: [id], onDelete: Cascade)
events JobEvent[]
@@index([jobId])
@@index([jobId, ordinal])
@@index([status])
@@map("job_steps")
}
model JobEvent {
id String @id @default(uuid()) @db.Uuid
jobId String @map("job_id") @db.Uuid
stepId String? @map("step_id") @db.Uuid
// Event details
type String
timestamp DateTime @db.Timestamptz
actor String
payload Json
// Relations
job RunnerJob @relation(fields: [jobId], references: [id], onDelete: Cascade)
step JobStep? @relation(fields: [stepId], references: [id], onDelete: Cascade)
@@index([jobId])
@@index([stepId])
@@index([timestamp])
@@index([type])
@@index([jobId, timestamp])
@@map("job_events")
}
// ============================================
// FEDERATION MODULE
// ============================================
model Instance {
id String @id @default(uuid()) @db.Uuid
instanceId String @unique @map("instance_id") // Unique identifier for federation
name String
url String
publicKey String @map("public_key") @db.Text
privateKey String @map("private_key") @db.Text // AES-256-GCM encrypted with ENCRYPTION_KEY
// Capabilities and metadata
capabilities Json @default("{}")
metadata Json @default("{}")
// Timestamps
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
@@map("instances")
}
model FederationConnection {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
// Remote instance details
remoteInstanceId String @map("remote_instance_id")
remoteUrl String @map("remote_url")
remotePublicKey String @map("remote_public_key") @db.Text
remoteCapabilities Json @default("{}") @map("remote_capabilities")
// Connection status
status FederationConnectionStatus @default(PENDING)
// Metadata
metadata Json @default("{}")
// Timestamps
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
connectedAt DateTime? @map("connected_at") @db.Timestamptz
disconnectedAt DateTime? @map("disconnected_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
messages FederationMessage[]
eventSubscriptions FederationEventSubscription[]
@@unique([workspaceId, remoteInstanceId])
@@index([workspaceId])
@@index([workspaceId, status])
@@index([remoteInstanceId])
@@map("federation_connections")
}
model FederatedIdentity {
id String @id @default(uuid()) @db.Uuid
localUserId String @map("local_user_id") @db.Uuid
remoteUserId String @map("remote_user_id")
remoteInstanceId String @map("remote_instance_id")
oidcSubject String @map("oidc_subject")
email String
metadata Json @default("{}")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
user User @relation(fields: [localUserId], references: [id], onDelete: Cascade)
@@unique([localUserId, remoteInstanceId])
@@index([localUserId])
@@index([remoteInstanceId])
@@index([oidcSubject])
@@map("federated_identities")
}
model FederationMessage {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
connectionId String @map("connection_id") @db.Uuid
// Message metadata
messageType FederationMessageType @map("message_type")
messageId String @unique @map("message_id") // UUID for deduplication
correlationId String? @map("correlation_id") // For request/response tracking
// Message content
query String? @db.Text
commandType String? @map("command_type") @db.Text
eventType String? @map("event_type") @db.Text // For EVENT messages
payload Json? @default("{}")
response Json? @default("{}")
// Status tracking
status FederationMessageStatus @default(PENDING)
error String? @db.Text
// Security
signature String @db.Text
// Timestamps
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
deliveredAt DateTime? @map("delivered_at") @db.Timestamptz
// Relations
connection FederationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@index([workspaceId])
@@index([connectionId])
@@index([messageId])
@@index([correlationId])
@@index([eventType])
@@map("federation_messages")
}
model FederationEventSubscription {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
connectionId String @map("connection_id") @db.Uuid
// Event subscription details
eventType String @map("event_type")
metadata Json @default("{}")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz
// Relations
connection FederationConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@unique([workspaceId, connectionId, eventType])
@@index([workspaceId])
@@index([connectionId])
@@index([eventType])
@@index([workspaceId, isActive])
@@map("federation_event_subscriptions")
}
// ============================================
// LLM USAGE TRACKING MODULE
// ============================================
model LlmUsageLog {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
userId String @map("user_id") @db.Uuid
// LLM provider and model info
provider String @db.VarChar(50)
model String @db.VarChar(100)
providerInstanceId String? @map("provider_instance_id") @db.Uuid
// Token usage
promptTokens Int @default(0) @map("prompt_tokens")
completionTokens Int @default(0) @map("completion_tokens")
totalTokens Int @default(0) @map("total_tokens")
// Optional cost (in cents for precision)
costCents Float? @map("cost_cents")
// Task type for routing analytics
taskType String? @map("task_type") @db.VarChar(50)
// Optional reference to conversation/session
conversationId String? @map("conversation_id") @db.Uuid
// Duration in milliseconds
durationMs Int? @map("duration_ms")
// Timestamp
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
user User @relation("UserLlmUsageLogs", fields: [userId], references: [id], onDelete: Cascade)
llmProviderInstance LlmProviderInstance? @relation("LlmUsageLogs", fields: [providerInstanceId], references: [id], onDelete: SetNull)
@@index([workspaceId])
@@index([workspaceId, createdAt])
@@index([userId])
@@index([provider])
@@index([model])
@@index([providerInstanceId])
@@index([taskType])
@@index([conversationId])
@@map("llm_usage_logs")
}
// ============================================
// TERMINAL MODULE
// ============================================
model TerminalSession {
id String @id @default(uuid()) @db.Uuid
workspaceId String @map("workspace_id") @db.Uuid
name String @default("Terminal")
status TerminalSessionStatus @default(ACTIVE)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz
closedAt DateTime? @map("closed_at") @db.Timestamptz
// Relations
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
@@index([workspaceId])
@@index([workspaceId, status])
@@map("terminal_sessions")
}