generator client { provider = "prisma-client-js" } datasource db { provider = "cockroachdb" url = env("DATABASE_URL") } model Server { id String @id @default(uuid()) hostname String @unique mac String? @unique cloud String @default("baremetal") environment String @default("default") role String @default("worker") labels Json @default("{}") ip String? agentVersion String? status String @default("unknown") // unknown, online, offline, provisioning lastHeartbeat DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt agent Agent? auditLogs AuditLog[] } model Agent { id String @id @default(uuid()) serverId String @unique server Server @relation(fields: [serverId], references: [id], onDelete: Cascade) certificatePem String? enrolledAt DateTime @default(now()) lastSeen DateTime? @@index([serverId]) } model User { id String @id @default(uuid()) username String @unique displayName String? certFingerprint String? @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt roleBindings UserRole[] auditLogs AuditLog[] } model Role { id String @id @default(uuid()) name String @unique description String? createdAt DateTime @default(now()) permissions Permission[] userBindings UserRole[] } model Permission { id String @id @default(uuid()) roleId String role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) type String @default("allow") // allow or deny action String // read, exec, apply, destroy, manage, admin, kubectl, * cloud String @default("*") environment String @default("*") server String @default("*") @@index([roleId]) } model UserRole { id String @id @default(uuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) roleId String role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) @@unique([userId, roleId]) @@index([userId]) @@index([roleId]) } model JoinToken { id String @id @default(uuid()) token String @unique type String @default("one-time") // one-time or reusable label String? usedBy String? // server hostname that used it usedAt DateTime? revokedAt DateTime? createdAt DateTime @default(now()) expiresAt DateTime? } model AuditLog { id String @id @default(uuid()) userId String? user User? @relation(fields: [userId], references: [id]) serverId String? server Server? @relation(fields: [serverId], references: [id]) sessionId String? action String // exec, kubectl, apply, login, rbac-denied, etc. resourceType String? // server, cluster, role, app, etc. resourceName String? args String? // sanitized command args result String @default("success") // success, denied, error durationMs Int? sourceIp String? timestamp DateTime @default(now()) @@index([userId]) @@index([serverId]) @@index([sessionId]) @@index([timestamp]) @@index([action]) } model PulumiRun { id String @id @default(uuid()) userId String stackName String action String // up, preview, destroy status String @default("pending") // pending, running, succeeded, failed output String? startedAt DateTime @default(now()) completedAt DateTime? @@index([userId]) @@index([stackName]) } model Cluster { id String @id @default(uuid()) name String @unique cloud String @default("baremetal") environment String @default("default") kubeconfigEnc String? // encrypted kubeconfig labels Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }