evOo-local/packages/db/prisma/schema.prisma
Claude a663764ee8
feat(media): contracts and foundation for Media Studio
Freeze the shared contracts every Media Studio slice builds on: zod DTOs,
EDL, platform profile, brand kit, gate and render plan schemas; the media
RPC surface with a scaffold router; provider interfaces for sources,
inference, rendering, motion graphics and publishing; media background
jobs; the Prisma models and migration; and the @rakazo/media domain
package signatures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138YqDj3ttfayRwuMKKrqVj
2026-10-03 17:32:52 +00:00

1925 lines
66 KiB
Plaintext

generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
}
model AiDataConsent {
userId String
spaceId String
recipientKey String
version String
grantedAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
spaceMember SpaceMember @relation(fields: [spaceId, userId], references: [spaceId, userId], onDelete: Cascade)
@@id([userId, spaceId, recipientKey])
@@map("ai_data_consents")
}
model User {
aiDataConsents AiDataConsent[]
id String @id
name String
email String
emailVerified Boolean @default(false)
image String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
avatarStyle String @default("robot")
sessions Session[]
accounts Account[]
members Member[]
invitations Invitation[]
modelCredentials UserModelCredential[]
voiceCredentials UserVoiceCredential[]
secrets Secret[]
mcpServers McpServer[]
botSections BotSection[]
actionApprovalRules ActionApprovalRule[]
actionAutoReviewPreferences ActionAutoReviewPreference[]
createdSpaces Space[] @relation("SpaceCreator")
botSecrets BotSecret[]
agentSecrets AgentSecret[]
profile UserProfile?
facts UserFact[]
@@unique([email])
@@map("user")
}
model Session {
id String @id
expiresAt DateTime
token String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ipAddress String?
userAgent String?
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
activeOrganizationId String?
@@unique([token])
@@index([userId])
@@map("session")
}
model Account {
id String @id
accountId String
providerId String
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
accessToken String?
refreshToken String?
idToken String?
accessTokenExpiresAt DateTime?
refreshTokenExpiresAt DateTime?
scope String?
password String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@map("account")
}
model Verification {
id String @id
identifier String
value String
expiresAt DateTime
createdAt DateTime? @default(now())
updatedAt DateTime? @updatedAt
@@index([identifier])
@@map("verification")
}
model Organization {
id String @id
name String
slug String
logo String?
createdAt DateTime
metadata String?
members Member[]
invitations Invitation[]
spaces Space[]
@@unique([slug])
@@map("organization")
}
model ActionApprovalRule {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
createdByUserId String
createdByUser User @relation(fields: [createdByUserId], references: [id], onDelete: Cascade)
effect String
matchKind String
matchValue String
createdAt DateTime @default(now())
@@unique([spaceId, createdByUserId, effect, matchKind, matchValue])
@@index([spaceId, createdByUserId])
@@map("action_approval_rules")
}
model ActionAutoReviewPreference {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
enabled Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([spaceId, userId])
@@map("action_auto_review_preferences")
}
model Member {
id String @id
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role String
createdAt DateTime
spaceMemberships SpaceMember[]
@@unique([organizationId, userId])
@@index([organizationId])
@@index([userId])
@@map("member")
}
model Space {
aiDataConsents AiDataConsent[]
id String @id
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
name String
workspacePath String?
gitRemoteUrl String?
gitDefaultBranch String?
gitHeadCommit String?
isDefault Boolean @default(false)
createdByUserId String?
createdByUser User? @relation("SpaceCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
deletingAt DateTime?
deletionClaimId String?
memberships SpaceMember[]
bots Bot[]
botSections BotSection[]
botDeletions BotDeletion[]
threads Thread[]
events Event[]
tasks Task[]
runs Run[]
routines Routine[]
scratchpadItems ScratchpadItem[]
projectGoals ProjectGoal[]
taughtSkills TaughtSkill[]
agentSkills AgentSkill[]
connections Connection[]
capabilities CapabilityInstall[]
memoryDocuments MemoryDocument[]
agentHomes AgentHome[]
browserProfiles BrowserProfile[]
artifacts Artifact[]
usageRecords UsageRecord[]
notificationPreferences NotificationPreference[]
computers Computer[]
externalEffects ExternalEffect[]
mcpServers McpServer[]
mcpOAuthSessions McpOAuthSession[]
botMcpServers BotMcpServer[]
messagingIdentities MessagingIdentity[]
actionApprovalRules ActionApprovalRule[]
actionAutoReviewPreferences ActionAutoReviewPreference[]
memoryConfig SpaceMemoryConfig?
secrets Secret[]
chatGroups ChatGroup[]
botSecrets BotSecret[]
externalConversations ExternalConversation[]
agentSecrets AgentSecret[]
userFactVisibility UserFactSpace[]
delegatedTasks DelegatedTask[]
semanticMemoryItems SemanticMemoryItem[]
mediaSources MediaSource[]
mediaAssets MediaAsset[]
mediaUploads MediaUpload[]
mediaSegments MediaSegment[]
mediaAnalyses MediaAnalysis[]
mediaCollections MediaCollection[]
mediaPeople MediaPerson[]
mediaFaceTracks MediaFaceTrack[]
mediaMarks MediaMark[]
mediaBrandKits MediaBrandKit[]
mediaProjects MediaProject[]
mediaEditPlans MediaEditPlan[]
mediaRenders MediaRender[]
mediaPublications MediaPublication[]
mediaMetrics MediaMetric[]
mediaMetricMappings MediaMetricMapping[]
mediaTeaserModel MediaTeaserModel?
@@unique([id, organizationId])
@@index([organizationId, createdAt])
@@index([createdByUserId])
@@map("spaces")
}
model SpaceMember {
id String @id
spaceId String
organizationId String
space Space @relation(fields: [spaceId, organizationId], references: [id, organizationId], onDelete: Cascade)
userId String
member Member @relation(fields: [organizationId, userId], references: [organizationId, userId], onDelete: Cascade)
role String @default("member")
createdAt DateTime
modelPreferences SpaceModelPreference[]
modelTiers SpaceModelTier[]
voicePreferences SpaceVoicePreference[]
aiDataConsents AiDataConsent[]
@@unique([spaceId, userId])
@@index([organizationId, userId])
@@index([userId, createdAt])
@@map("space_members")
}
model Invitation {
id String @id
organizationId String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
email String
role String?
status String
expiresAt DateTime
createdAt DateTime @default(now())
inviterId String
user User @relation(fields: [inviterId], references: [id], onDelete: Cascade)
@@index([organizationId])
@@index([email])
@@map("invitation")
}
model DeploymentSettings {
id String @id @default("default")
ownerUserId String?
signupsEnabled Boolean @default(true)
signupAllowlist String @default("")
signupPolicyInitialized Boolean @default(false)
defaultModelProvider String?
defaultModelId String?
deploymentModelCredentialCipher String?
computerHost String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("deployment_settings")
}
model UserModelCredential {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
provider String
label String
secretId String
supportsImages Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
preferences SpaceModelPreference[]
tiers SpaceModelTier[]
@@unique([id, userId])
@@index([userId, provider, updatedAt])
@@map("user_model_credentials")
}
model UserVoiceCredential {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
provider String
secretId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
preferences SpaceVoicePreference[]
@@unique([id, userId])
@@index([userId, provider, updatedAt])
@@map("user_voice_credentials")
}
model SpaceModelPreference {
id String @id @default(cuid())
spaceId String
userId String
spaceMember SpaceMember @relation(fields: [spaceId, userId], references: [spaceId, userId], onDelete: Cascade)
credentialId String
credential UserModelCredential @relation(fields: [credentialId, userId], references: [id, userId], onDelete: Cascade)
modelId String?
isDefault Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([spaceId, userId, credentialId])
@@index([spaceId, userId, isDefault, updatedAt])
@@index([credentialId])
// Partial unique index space_model_preferences_default_key is created in SQL.
@@map("space_model_preferences")
}
/// Maps a model tier (fast, standard, code) to a connected model for one user in one space.
model SpaceModelTier {
id String @id @default(cuid())
spaceId String
userId String
spaceMember SpaceMember @relation(fields: [spaceId, userId], references: [spaceId, userId], onDelete: Cascade)
tier String
credentialId String
credential UserModelCredential @relation(fields: [credentialId, userId], references: [id, userId], onDelete: Cascade)
modelId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([spaceId, userId, tier])
@@index([credentialId])
// CHECK constraint space_model_tiers_tier_chk is created in SQL.
@@map("space_model_tiers")
}
model SpaceVoicePreference {
id String @id @default(cuid())
spaceId String
userId String
spaceMember SpaceMember @relation(fields: [spaceId, userId], references: [spaceId, userId], onDelete: Cascade)
credentialId String
credential UserVoiceCredential @relation(fields: [credentialId, userId], references: [id, userId], onDelete: Cascade)
voiceId String @default("")
isDefault Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([spaceId, userId, credentialId])
@@index([spaceId, userId, isDefault, updatedAt])
@@index([credentialId])
// Partial unique index space_voice_preferences_default_key is created in SQL.
@@map("space_voice_preferences")
}
model Bot {
screenGeneration Int @default(0)
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
name String
title String @default("")
description String @default("")
instructions String @default("")
color String
notifyOnFinish Boolean @default(true)
pinned Boolean @default(false)
position Int @default(0)
sectionId String?
section BotSection? @relation(fields: [sectionId], references: [id], onDelete: SetNull)
archivedAt DateTime?
parentBotId String?
spawnKey String?
memoryScope String?
parent Bot? @relation("BotChildren", fields: [parentBotId], references: [id], onDelete: SetNull)
children Bot[] @relation("BotChildren")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
thread Thread?
computerId String?
computer Computer? @relation(fields: [computerId], references: [id], onDelete: SetNull)
computerSwitching Boolean @default(false)
voiceId String?
autoSpeak Boolean @default(false)
modelProvider String?
modelId String?
thinkingLevel String?
webhookSecretId String?
teamChatAmbientEnabled Boolean @default(false)
teamChatRules String @default("")
/// "main" for the user's single lead agent, "specialist" for every other bot.
kind String @default("specialist")
tags String[] @default([])
restrictBuiltinTools Boolean @default(false)
allowedBuiltinTools String[] @default([])
modelTier String?
routines Routine[]
scratchpadItems ScratchpadItem[]
taughtSkills TaughtSkill[]
memoryDocuments MemoryDocument[]
agentHome AgentHome?
browserProfile BrowserProfile?
artifacts Artifact[]
tasks Task[]
runs Run[]
steeringMessages SteeringMessage[]
mcpServers BotMcpServer[]
groupMembers ChatGroupMember[]
ledGroups ChatGroup[] @relation("ChatGroupLead")
semanticMemoryItems SemanticMemoryItem[]
botSecrets BotSecret[]
externalConversations ExternalConversation[]
@@unique([spaceId, spawnKey])
// CHECK bots_kind_chk and partial unique index bots_userId_main_key are created in SQL.
@@index([spaceId, userId, archivedAt, pinned, updatedAt])
@@index([sectionId])
@@index([computerId])
@@index([parentBotId])
@@map("bots")
}
model BotSection {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
name String
position Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bots Bot[]
groups ChatGroup[]
projectGoals ProjectGoal[]
@@unique([spaceId, userId, name])
@@index([spaceId, userId, position, createdAt])
@@map("bot_sections")
}
model BotDeletion {
id String @id
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
name String
deletedByUserId String
memoriesPreserved Boolean
deletedAt DateTime @default(now())
@@index([spaceId, deletedAt])
@@map("bot_deletions")
}
model ChatGroup {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
name String
/// "main" for the user's main conversation led by the main bot, "group" otherwise.
kind String @default("group")
leadBotId String?
leadBot Bot? @relation("ChatGroupLead", fields: [leadBotId], references: [id], onDelete: SetNull)
pinned Boolean @default(false)
sectionId String?
section BotSection? @relation(fields: [sectionId], references: [id], onDelete: SetNull)
archivedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
members ChatGroupMember[]
thread Thread?
artifacts Artifact[]
projectGoals ProjectGoal[]
@@index([spaceId, userId, archivedAt, pinned, updatedAt])
@@index([sectionId])
@@index([leadBotId])
// CHECK chat_groups_kind_chk and partial unique index chat_groups_userId_main_key are created in SQL.
@@map("chat_groups")
}
model ChatGroupMember {
id String @id @default(cuid())
groupId String
group ChatGroup @relation(fields: [groupId], references: [id], onDelete: Cascade)
botId String
bot Bot @relation(fields: [botId], references: [id], onDelete: NoAction)
createdAt DateTime @default(now())
@@unique([groupId, botId])
@@index([botId])
@@map("chat_group_members")
}
model Thread {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String? @unique
bot Bot? @relation(fields: [botId], references: [id], onDelete: Cascade)
groupId String? @unique
group ChatGroup? @relation(fields: [groupId], references: [id], onDelete: Cascade)
externalConversationId String? @unique
externalConversation ExternalConversation? @relation(fields: [externalConversationId], references: [id], onDelete: Cascade)
userId String
createdAt DateTime @default(now())
nextEventSeq Int @default(0)
nextMessageSeq Int @default(0)
historyCompactedUpToSeq Int?
historyCompactionSummary String? @db.Text
historyCompactionGeneration Int @default(0)
unread Boolean @default(false)
messages Message[]
events Event[]
tasks Task[]
runs Run[]
routines Routine[]
delegatedTasks DelegatedTask[]
@@index([spaceId])
@@map("threads")
}
model Message {
id String @id @default(cuid())
threadId String
thread Thread @relation(fields: [threadId], references: [id], onDelete: Cascade)
seq Int
role String
blocks Json
botId String?
replyToMessageId String?
replyQuote String?
replyTo Message? @relation("MessageReplies", fields: [replyToMessageId], references: [id], onDelete: SetNull)
replies Message[] @relation("MessageReplies")
runId String?
clientNonce String?
sourceRuns Run[] @relation("RunSourceMessage")
externalTranscriptFor ExternalMessage?
steeringMessages SteeringMessage[]
createdAt DateTime @default(now())
@@unique([threadId, seq])
@@unique([threadId, clientNonce])
@@index([threadId, seq])
@@index([replyToMessageId])
@@map("messages")
}
model Event {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
threadId String
thread Thread @relation(fields: [threadId], references: [id], onDelete: Cascade)
botId String
seq Int
type String
payload Json
runId String?
createdAt DateTime @default(now())
@@unique([threadId, seq])
@@index([spaceId, createdAt])
@@index([threadId, seq])
@@index([runId, type, seq])
@@map("events")
}
model Task {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
threadId String
thread Thread @relation(fields: [threadId], references: [id], onDelete: Cascade)
userId String
prompt String
status String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
runs Run[]
@@index([spaceId, botId])
@@map("tasks")
}
model Run {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
threadId String
thread Thread @relation(fields: [threadId], references: [id], onDelete: Cascade)
taskId String
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
userId String
status String
trigger String
modelProvider String?
modelId String?
error String?
leaseOwner String?
leaseFence Int @default(0)
leaseExpiresAt DateTime?
checkpoint String?
clientNonce String?
sourceMessageId String?
sourceMessage Message? @relation("RunSourceMessage", fields: [sourceMessageId], references: [id], onDelete: SetNull)
routineId String?
routine Routine? @relation(fields: [routineId], references: [id], onDelete: SetNull)
startedAt DateTime?
completedAt DateTime?
toolCallBudgetCount Int?
toolCallBudgetExceeded Boolean?
botOutcomeReturnedAt DateTime?
messagingMirroredAt DateTime?
teamChatMirroredAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
attempts Attempt[]
effects ExternalEffect[]
usageRecords UsageRecord[]
steeringMessages SteeringMessage[]
externalMessage ExternalMessage?
@@unique([spaceId, clientNonce])
@@index([status, leaseExpiresAt])
@@index([status, updatedAt, id])
@@index([botOutcomeReturnedAt, status])
@@index([trigger, status, messagingMirroredAt, updatedAt])
@@index([trigger, status, teamChatMirroredAt, updatedAt])
@@index([spaceId, botId])
@@index([sourceMessageId])
@@index([threadId, status, createdAt])
@@index([routineId])
@@map("runs")
}
model SteeringMessage {
id String @id @default(cuid())
messageId String
message Message @relation(fields: [messageId], references: [id], onDelete: Cascade)
botId String
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
userId String
runId String?
run Run? @relation(fields: [runId], references: [id], onDelete: SetNull)
claimedAt DateTime?
createdAt DateTime @default(now())
@@unique([messageId, botId])
@@index([botId, runId, createdAt])
@@index([runId])
@@map("steering_messages")
}
model Attempt {
id String @id @default(cuid())
runId String
run Run @relation(fields: [runId], references: [id], onDelete: Cascade)
fence Int
status String
error String?
startedAt DateTime @default(now())
finishedAt DateTime?
@@index([runId])
@@map("attempts")
}
// Deliberately independent of chat/user cascades: the worker must still be able
// to cancel remote work after its originating chat or account is deleted.
model CloudAgent {
id String @id @default(uuid())
operationKey String @unique
providerKey String
spaceId String
userId String
botId String
threadId String
messageId String?
remoteId String?
latestRunId String?
title String
status String @default("running")
url String @default("")
branch String?
prUrl String?
launchRequest Json
launchDispatched Boolean @default(false)
followup Json?
followupDispatching Boolean @default(false)
cancelRequested Boolean @default(false)
generation Int @default(0)
wakeGeneration Int @default(-1)
version Int @default(0)
leaseToken String?
leaseExpiresAt DateTime?
nextPollAt DateTime? @default(now())
errorCount Int @default(0)
createdAt DateTime @default(now())
@@index([providerKey, nextPollAt])
@@index([spaceId, userId, id])
@@map("cloud_agents")
}
model ExternalEffect {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
runId String
run Run @relation(fields: [runId], references: [id], onDelete: Cascade)
kind String
idempotencyKey String
status String
request Json
result Json?
reviewDecision String?
reviewReason String?
reviewModel String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([idempotencyKey])
@@index([runId, status])
@@map("external_effects")
}
model Routine {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
userId String
threadId String?
thread Thread? @relation(fields: [threadId], references: [id], onDelete: SetNull)
/// The main bot when it scheduled this routine for another bot; null when the bot or the user created it.
createdByBotId String?
name String
prompt String
crons String[]
timezone String @default("UTC")
active Boolean @default(false)
notify Boolean @default(true)
webhookEnabled Boolean @default(false)
githubEnabled Boolean @default(false)
messageProvider String?
lastRunAt DateTime?
nextRunAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
runs Run[]
@@index([active, nextRunAt])
@@index([spaceId, botId])
@@index([createdByBotId])
@@map("routines")
}
model ScratchpadItem {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
userId String
title String
status String @default("open")
notes String @default("")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([spaceId, botId, status])
@@index([spaceId, botId, updatedAt])
@@map("scratchpad_items")
}
model ProjectGoal {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
sectionId String
section BotSection @relation(fields: [sectionId], references: [id], onDelete: Cascade)
groupId String?
group ChatGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)
title String
description String?
status String @default("open")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([spaceId, sectionId, status])
@@index([spaceId, userId, sectionId, updatedAt])
@@map("project_goals")
}
model TaughtSkill {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
userId String
name String @default("")
goal String
status String
playbook Json @default("{}")
recording Json @default("{\"events\":[],\"snapshots\":[]}")
startedAt DateTime?
expiresAt DateTime?
stoppedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([spaceId, botId])
@@index([spaceId, botId, status])
// Partial unique index taught_skills_botId_recording_key is created in SQL:
// one recording session per bot.
@@map("taught_skills")
}
/// Claude Agent Skills: SKILL.md recipes shared across assistants (not per-bot).
/// The Pi runtime already understands this format; we persist and inject them.
/// Distinct from TaughtSkill (demo/record playbooks).
model AgentSkill {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
name String
description String
content String
/// user | builtin | plugin — only user skills are writable via API/tools.
source String @default("user")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([spaceId, userId])
// Case-insensitive unique index agent_skills_spaceId_userId_name_lower_key is created in SQL.
@@map("agent_skills")
}
model Connection {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
connectorId String @default("composio")
provider String
displayName String
status String
secretId String?
providerRef String?
metadata Json @default("{}")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([spaceId, userId, connectorId])
@@map("connections")
}
model CapabilityInstall {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
kind String
name String
source String
version String?
digest String?
secretId String?
config Json @default("{}")
createdAt DateTime @default(now())
@@index([spaceId, userId])
@@map("capability_installs")
}
model MemoryDocument {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
botId String?
bot Bot? @relation(fields: [botId], references: [id], onDelete: Cascade)
scope String
path String
content String
revision Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
revisions MemoryRevision[]
@@unique([spaceId, scope, botId, path])
@@index([spaceId, userId])
@@map("memory_documents")
}
model MemoryRevision {
id String @id @default(cuid())
documentId String
document MemoryDocument @relation(fields: [documentId], references: [id], onDelete: Cascade)
revision Int
content String
sourceRunId String?
sourceThreadId String?
createdAt DateTime @default(now())
@@unique([documentId, revision])
@@map("memory_revisions")
}
/// One item of the built-in semantic memory, recalled by cosine similarity computed in process.
model SemanticMemoryItem {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
/// Shared items are also recalled by the Space's other bots when they use shared scope.
shared Boolean @default(false)
/// Compacted-history generation of the bot's thread; null for durable memories.
generation Int?
content String
embedding Float[]
embeddingModel String
createdAt DateTime @default(now())
@@index([spaceId, botId, generation, createdAt])
@@index([spaceId, shared, createdAt])
@@map("semantic_memory_items")
}
model AgentHome {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String @unique
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
userId String
revision String @default("empty")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("agent_homes")
}
model BrowserProfile {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String @unique
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
userId String
secretId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("browser_profiles")
}
model ComputerUpdate {
action String @default("update")
id String @id @default(cuid())
computerId String
computer Computer @relation(fields: [computerId], references: [id], onDelete: Cascade)
botId String
status String @default("queued")
stage String @default("preparing")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([computerId, createdAt])
@@index([status, updatedAt])
@@map("computer_updates")
}
model Computer {
screenGeneration Int @default(0)
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
scope String @default("team")
scopeKey String @unique
homeKey String @unique
homeRevision String @default("empty")
kind String
providerRef String?
state String @default("stopped")
controlHolder String @default("none")
controlLeaseId String?
controlLeaseExpiresAt DateTime?
controlBotId String?
controlRunId String?
controlFence Int @default(0)
executionRunId String?
executionBotId String?
executionLeaseExpiresAt DateTime?
executionFence Int @default(0)
screenUrl String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
bots Bot[]
executionLeases ComputerExecutionLease[]
maintenanceId String?
updates ComputerUpdate[]
@@index([spaceId, scope])
@@map("computers")
}
model ComputerExecutionLease {
id String @id @default(cuid())
computerId String
computer Computer @relation(fields: [computerId], references: [id], onDelete: Cascade)
botId String
runId String
fence Int @default(0)
expiresAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([computerId, botId])
@@index([runId])
@@index([computerId, expiresAt])
@@map("computer_execution_leases")
}
model Artifact {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String?
bot Bot? @relation(fields: [botId], references: [id], onDelete: SetNull)
groupId String?
group ChatGroup? @relation(fields: [groupId], references: [id], onDelete: Cascade)
userId String
runId String?
name String
mimeType String
size Int
hash String
storageKey String
createdAt DateTime @default(now())
@@index([spaceId, botId])
@@index([groupId])
@@map("artifacts")
}
model UsageRecord {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
/// Which bot spent this. Deliberately NOT a relation: a usage row is spend that really
/// happened, and no lifecycle event on the bot may delete it or blank its attribution.
/// `Cascade` erased the row; `SetNull` would erase the attribution instead, and because
/// deleting a bot also deletes its runs, `runId` goes null in the same breath — the row
/// would keep only space, user, model and token counts.
///
/// So the id outlives the bot and may dangle. Read it with a LEFT JOIN; `BotDeletion.id`
/// IS the bot id, so a deleted bot's name is still reachable for display.
botId String?
userId String
runId String?
run Run? @relation(fields: [runId], references: [id], onDelete: SetNull)
provider String
model String
inputTokens Int
outputTokens Int
createdAt DateTime @default(now())
@@index([spaceId, userId, createdAt])
@@map("usage_records")
}
model NotificationPreference {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
finish Boolean @default(true)
help Boolean @default(true)
takeover Boolean @default(true)
@@unique([spaceId, userId])
@@map("notification_preferences")
}
model SpaceMemoryConfig {
id String @id @default(cuid())
spaceId String @unique
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
provider String
settings Json
secretId String @unique
secret Secret @relation(fields: [secretId], references: [id], onDelete: Cascade)
defaultMemoryScope String @default("isolated")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("space_memory_configs")
}
model Secret {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
spaceId String?
space Space? @relation(fields: [spaceId], references: [id], onDelete: Cascade)
kind String
ciphertext String
createdAt DateTime @default(now())
mcpServers McpServer[]
spaceMemoryConfig SpaceMemoryConfig?
agentSecret AgentSecret?
@@index([userId, spaceId])
@@map("secrets")
}
model McpServer {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
slug String
name String
description String @default("")
transport String
endpoint String?
command String?
args Json @default("[]")
env Json @default("{}")
headers Json @default("{}")
secretId String?
secret Secret? @relation(fields: [secretId], references: [id], onDelete: SetNull)
enabled Boolean @default(true)
revision Int @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
assignments BotMcpServer[]
oauthSessions McpOAuthSession[]
@@unique([spaceId, userId, slug])
@@index([spaceId, userId])
@@map("mcp_servers")
}
model McpOAuthSession {
id String @id
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
userId String
serverId String
server McpServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
endpoint String
redirectUri String
oauthCiphertext String
createdAt DateTime @default(now())
@@index([spaceId, userId, createdAt])
@@map("mcp_oauth_sessions")
}
model BotMcpServer {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
serverId String
server McpServer @relation(fields: [serverId], references: [id], onDelete: Cascade)
userId String
allowAllTools Boolean @default(true)
allowedTools Json @default("[]")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([botId, serverId])
@@index([spaceId, userId, botId])
@@map("bot_mcp_servers")
}
model MessagingIdentity {
id String @id @default(cuid())
provider String
/// Sender address within the provider: E.164 number, Slack user id, ….
address String
/// Opaque provider thread id for the 1:1 conversation; learned on inbound.
dmThreadId String?
userId String
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String @unique
verifiedAt DateTime?
lastInboundAt DateTime?
outboundSinceInbound Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([provider, address])
@@index([userId])
@@map("messaging_identities")
}
model MessagingChannel {
id String @id @default(cuid())
provider String
/// Opaque provider thread id of the group conversation.
threadId String @unique
name String?
introPostedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
members MessagingChannelMember[]
@@map("messaging_channels")
}
model MessagingChannelMember {
id String @id @default(cuid())
channelId String
channel MessagingChannel @relation(fields: [channelId], references: [id], onDelete: Cascade)
address String
identityId String?
status String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([channelId, address])
@@index([identityId])
@@map("messaging_channel_members")
}
/// Short-lived web-issued code binding a chat address to an existing user+bot.
model MessagingLinkCode {
id String @id @default(cuid())
code String @unique
userId String
spaceId String
botId String
expiresAt DateTime
createdAt DateTime @default(now())
@@index([userId])
@@map("messaging_link_codes")
}
model MessagingOutbound {
id String @id @default(cuid())
idempotencyKey String @unique
kind String
/// DM rows resolve their thread through the identity at drain time.
identityId String?
/// Group/intro rows post straight to this provider thread id.
threadId String?
body String
status String @default("pending")
attempts Int @default(0)
nextAttemptAt DateTime?
providerHandle String?
sourceMessageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([status, nextAttemptAt])
@@map("messaging_outbound")
}
model AgentConnection {
id String @id @default(cuid())
requesterBotId String
targetBotId String
status String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([requesterBotId, targetBotId])
@@index([targetBotId, status])
@@map("agent_connections")
}
model BotSecret {
id String @id
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
name String
origin String
auth Json
ciphertext String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, spaceId, botId, name])
@@index([botId])
@@index([spaceId])
@@map("bot_secrets")
}
model ExternalConversation {
id String @id @default(cuid())
provider String
workspaceId String
externalKey String
conversationId String
displayName String?
participantNames String[] @default([])
teamChatAmbientEnabled Boolean?
teamChatRules String?
automatedSenderPolicies Json @default("{}")
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
botId String
bot Bot @relation(fields: [botId], references: [id], onDelete: Cascade)
userId String
thread Thread?
messages ExternalMessage[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([provider, workspaceId, externalKey])
@@index([spaceId, botId])
@@map("external_conversations")
}
model ExternalMessage {
id String @id @default(cuid())
externalConversationId String
externalConversation ExternalConversation @relation(fields: [externalConversationId], references: [id], onDelete: Cascade)
providerEventId String
kind String @default("direct")
senderId String
senderName String
senderIsBot Boolean @default(false)
content String
replyThreadId String?
batchContext String?
engagementReason String?
judgedAt DateTime?
status String @default("received")
runId String? @unique
run Run? @relation(fields: [runId], references: [id], onDelete: SetNull)
threadMessageId String? @unique
threadMessage Message? @relation(fields: [threadMessageId], references: [id], onDelete: SetNull)
attempts Int @default(0)
nextAttemptAt DateTime?
lastError String?
providerReplyHandle String?
deliveredAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([externalConversationId, providerEventId])
@@index([status, nextAttemptAt])
@@map("external_messages")
}
model AgentSecret {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
createdByUserId String
createdByUser User @relation(fields: [createdByUserId], references: [id], onDelete: Cascade)
name String
secretId String @unique
secret Secret @relation(fields: [secretId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([spaceId, name])
@@index([spaceId, updatedAt])
@@map("agent_secrets")
}
// Deployment-owned integration credentials; never exposed through read DTOs.
model IntegrationProviderConfig {
id String @id
ciphertext String
updatedAt DateTime @updatedAt
@@map("integration_provider_configs")
}
/// What the user's agents know about the user across spaces. One row per user.
model UserProfile {
userId String @id
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
timeZone String?
locale String?
language String?
communicationStyle String @default("")
summary String @default("")
consolidatedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("user_profiles")
}
/// One durable statement about the user, with its origin and consent state.
model UserFact {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
category String
value String
confidence Float @default(1)
source String
status String @default("active")
sensitive Boolean @default(false)
allSpaces Boolean @default(true)
sourceBotId String?
sourceThreadId String?
sourceMessageId String?
embedding Float[] @default([])
embeddingModel String?
confirmedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
visibleSpaces UserFactSpace[]
@@index([userId, status, category, updatedAt])
// CHECK constraints user_facts_category_chk, user_facts_source_chk and user_facts_status_chk are created in SQL.
@@map("user_facts")
}
/// Spaces a fact is limited to when UserFact.allSpaces is false.
model UserFactSpace {
factId String
fact UserFact @relation(fields: [factId], references: [id], onDelete: Cascade)
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
@@id([factId, spaceId])
@@index([spaceId])
@@map("user_fact_spaces")
}
/// Work the main bot hands to a specialist, reported back in the main conversation.
model DelegatedTask {
id String @id @default(cuid())
userId String
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
threadId String
thread Thread @relation(fields: [threadId], references: [id], onDelete: Cascade)
requesterBotId String
assigneeBotId String
assigneeSpaceId String
title String
brief String
status String @default("queued")
deadlineAt DateTime?
result String?
artifacts Json @default("[]")
runId String? @unique
messageId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
completedAt DateTime?
@@index([threadId, status, createdAt])
@@index([assigneeBotId, status])
@@index([userId, status, updatedAt])
// CHECK constraint delegated_tasks_status_chk is created in SQL.
@@map("delegated_tasks")
}
// ---------------------------------------------------------------------------
// Media Studio
// ---------------------------------------------------------------------------
model MediaSource {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
ownerUserId String
kind String
label String
/// Folder: { path }. Client-pushed: { deviceId }. Never secrets.
config Json @default("{}")
status String @default("idle")
error String?
lastSyncCursor String?
lastSyncedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
assets MediaAsset[]
@@index([spaceId, createdAt])
@@map("media_sources")
}
model MediaAsset {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
sourceId String
source MediaSource @relation(fields: [sourceId], references: [id], onDelete: Cascade)
externalId String
revision String?
contentHash String?
kind String
storage String
/// Uploaded originals: path relative to the media root. Referenced originals resolve through the source.
storagePath String?
fileName String?
sizeBytes BigInt?
capturedAt DateTime?
durationMs Int?
width Int?
height Int?
fps Float?
codec String?
orientation Int?
/// Rounded to two decimals (about 1 km).
gpsLat Float?
gpsLon Float?
deviceModel String?
albumNames String[] @default([])
isFavorite Boolean @default(false)
proxyPath String?
thumbPath String?
spritePath String?
qualityScore Float?
analysisVersion Int @default(0)
ingestState String @default("indexed")
ingestError String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
segments MediaSegment[]
analyses MediaAnalysis[]
collectionItems MediaCollectionItem[]
faceTracks MediaFaceTrack[]
marks MediaMark[]
uploads MediaUpload[]
@@unique([sourceId, externalId])
@@index([spaceId, capturedAt])
@@index([spaceId, kind, capturedAt])
@@index([spaceId, ingestState])
@@index([contentHash])
@@map("media_assets")
}
model MediaUpload {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
assetId String
asset MediaAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
userId String
sizeBytes BigInt
chunkBytes Int
sha256 String
fileName String
receivedChunks Int[] @default([])
status String @default("open")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([assetId])
@@index([spaceId, status])
@@map("media_uploads")
}
model MediaSegment {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
assetId String
asset MediaAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
kind String
startMs Int
endMs Int
/// Partial<Record<TeaserFeature, number>>
scores Json @default("{}")
teaserScore Float?
transcriptExcerpt String?
labels String[] @default([])
paidOnly Boolean @default(false)
analysisVersion Int
createdAt DateTime @default(now())
@@index([assetId, startMs])
@@index([spaceId, teaserScore])
@@map("media_segments")
}
model MediaAnalysis {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
assetId String
asset MediaAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
kind String
modelId String
modelVersion String
/// Small results inline; large results as a path relative to the media root.
data Json?
dataPath String?
/// Float32 little-endian vectors for embeddings.
vectors Bytes?
createdAt DateTime @default(now())
@@unique([assetId, kind, modelVersion])
@@index([spaceId, kind])
@@map("media_analyses")
}
model MediaCollection {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
kind String
title String
labelSource String @default("rule")
startAt DateTime?
endAt DateTime?
centroidLat Float?
centroidLon Float?
coverAssetId String?
/// Stable key so re-clustering updates instead of recreating user-renamed collections.
clusterKey String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
items MediaCollectionItem[]
@@unique([spaceId, kind, clusterKey])
@@index([spaceId, kind, startAt])
@@map("media_collections")
}
model MediaCollectionItem {
collectionId String
collection MediaCollection @relation(fields: [collectionId], references: [id], onDelete: Cascade)
assetId String
asset MediaAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
position Int
@@id([collectionId, assetId])
@@index([assetId])
@@map("media_collection_items")
}
model MediaPerson {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
displayName String
isSelf Boolean @default(false)
confirmed Boolean @default(false)
anonymizeByDefault String @default("none")
/// Mean identity embedding (Float32). Biometric: local only, deleted with the person.
centroid Bytes?
consentArtifactId String?
consentRecordedAt DateTime?
consentRecordedBy String?
ageVerifiedAt DateTime?
ageVerificationMethod String @default("none")
/// Seed for a stable synthetic identity.
syntheticSeed Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
faceTracks MediaFaceTrack[]
marks MediaMark[]
@@index([spaceId])
@@map("media_people")
}
model MediaFaceTrack {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
assetId String
asset MediaAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
personId String?
person MediaPerson? @relation(fields: [personId], references: [id], onDelete: SetNull)
startMs Int
endMs Int
confidenceMin Float
/// Box keyframes JSON relative to the media root.
keyframesPath String
/// Biometric (Float32). Local only.
embedding Bytes?
modelVersion String
createdAt DateTime @default(now())
@@index([assetId])
@@index([personId])
@@map("media_face_tracks")
}
model MediaMark {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
kind String
label String?
personId String?
person MediaPerson? @relation(fields: [personId], references: [id], onDelete: Cascade)
assetId String?
asset MediaAsset? @relation(fields: [assetId], references: [id], onDelete: Cascade)
/// MediaMarkPrompt
prompt Json?
treatment String
maskDir String?
qualityScore Float?
suggestedFallback String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([spaceId, personId])
@@index([assetId])
@@map("media_marks")
}
model MediaBrandKit {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
/// BrandKit
kit Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
projects MediaProject[]
@@index([spaceId])
@@map("media_brand_kits")
}
model MediaProject {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
createdBy String
title String
goal String
status String @default("draft")
brandKitId String?
brandKit MediaBrandKit? @relation(fields: [brandKitId], references: [id], onDelete: SetNull)
collectionIds String[] @default([])
assetIds String[] @default([])
profileIds String[] @default([])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
plans MediaEditPlan[]
renders MediaRender[]
@@index([spaceId, updatedAt])
@@map("media_projects")
}
model MediaEditPlan {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
projectId String
project MediaProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
profileId String
version Int
/// Edl, validated before insert.
edl Json
createdBy String
rationale String?
createdAt DateTime @default(now())
renders MediaRender[]
@@unique([projectId, profileId, version])
@@map("media_edit_plans")
}
model MediaRender {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
projectId String
project MediaProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
planId String
plan MediaEditPlan @relation(fields: [planId], references: [id], onDelete: Cascade)
profileId String
quality String
status String @default("queued")
progress Float @default(0)
/// MediaRenderOutput[] plus internal relative paths.
outputs Json @default("[]")
gateReport Json?
ffprobe Json?
autoFixRound Int @default(0)
error String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
publications MediaPublication[]
@@index([projectId, createdAt])
@@index([spaceId, status])
@@map("media_renders")
}
model MediaPublication {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
renderId String
render MediaRender @relation(fields: [renderId], references: [id], onDelete: Cascade)
profileId String
destination String
status String @default("draft")
scheduledAt DateTime?
publishedAt DateTime?
externalUrl String?
targetUrl String?
trackingUrl String?
utm Json?
title String?
caption String?
tags String[] @default([])
thumbnailVariant String?
bundleArtifactId String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
metrics MediaMetric[]
@@index([spaceId, createdAt])
@@index([renderId])
@@map("media_publications")
}
model MediaMetric {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
publicationId String
publication MediaPublication @relation(fields: [publicationId], references: [id], onDelete: Cascade)
observedAt DateTime
views Int @default(0)
clicks Int @default(0)
conversions Int @default(0)
revenueCents Int @default(0)
currency String @default("EUR")
source String
createdAt DateTime @default(now())
@@unique([publicationId, observedAt, source])
@@index([spaceId, observedAt])
@@map("media_metrics")
}
model MediaMetricMapping {
id String @id @default(cuid())
spaceId String
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
name String
/// MetricCsvMapping without id
mapping Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([spaceId, name])
@@map("media_metric_mappings")
}
model MediaTeaserModel {
spaceId String @id
space Space @relation(fields: [spaceId], references: [id], onDelete: Cascade)
/// TeaserWeights
weights Json
sampleCount Int
updatedAt DateTime @updatedAt
@@map("media_teaser_models")
}