Files
stack/apps/api/prisma/schema.prisma
Jason Woltje 7d22c2490a
Some checks failed
ci/woodpecker/push/api Pipeline failed
feat(#380): Workspace-to-Matrix-Room mapping and provisioning
- Add matrix_room_id column to workspace table (migration)
- Create MatrixRoomService for room provisioning and mapping
- Auto-create Matrix room on workspace provisioning (when configured)
- Support manual room linking for existing workspaces
- Unit tests for all mapping operations

Refs #380
2026-02-15 02:16:29 -06:00

1510 lines
47 KiB
Plaintext

// Mosaic Stack Database Schema
// PostgreSQL 17 with pgvector extension
generator client {
provider = "prisma-client-js"
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
}
// ============================================
// 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[]
@@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")
}