generator client { provider = "prisma-client-js" } datasource db { provider = "cockroachdb" url = env("DATABASE_URL") } // ── Auth (mcpctl pattern: email/password + bearer token sessions) ── model User { id String @id @default(cuid()) email String @unique password String // bcrypt name String? role UserRole @default(USER) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt sessions Session[] auditLogs AuditEvent[] groups GroupMember[] } enum UserRole { USER ADMIN } model Session { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) token String @unique expiresAt DateTime createdAt DateTime @default(now()) @@index([userId]) @@index([token]) } model Group { id String @id @default(cuid()) name String @unique description String? createdAt DateTime @default(now()) members GroupMember[] } model GroupMember { id String @id @default(cuid()) groupId String group Group @relation(fields: [groupId], references: [id], onDelete: Cascade) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([groupId, userId]) } model ServiceAccount { id String @id @default(cuid()) name String @unique token String @unique createdAt DateTime @default(now()) } // ── RBAC (mcpctl pattern: named definitions with JSON subjects/bindings) ── model RbacDefinition { id String @id @default(cuid()) name String @unique subjects Json // [{kind: "User"|"Group"|"ServiceAccount", name: string}] roleBindings Json // [{role, resource, name?, environment?, action?}] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // ── Audit (mcpctl pattern: fire-and-forget with correlation IDs) ── model AuditEvent { id String @id @default(cuid()) timestamp DateTime @default(now()) eventKind String source String // cli | labd | agent | driver | fleet-controller | sync-controller verified Boolean @default(false) userId String? user User? @relation(fields: [userId], references: [id]) userName String? sessionId String? environmentName String? accountName String? resourceKind String? resourceName String? correlationId String parentEventId String? details Json @default("{}") result String // success | failure | denied | skipped error String? durationMs Int? @@index([correlationId]) @@index([eventKind, timestamp]) @@index([environmentName, timestamp]) @@index([resourceKind, resourceName]) @@index([userId, timestamp]) } // ── Core infrastructure ── model Environment { id String @id @default(cuid()) name String @unique status String @default("active") // active | archived createdAt DateTime @default(now()) updatedAt DateTime @updatedAt bindings Binding[] resources Resource[] } model Account { id String @id @default(cuid()) name String @unique driver String // baremetal-pxe | aws | gcp | kubernetes | ovh config Json @default("{}") // Credentials stored in Infisical, referenced by secretPath secretPath String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt bindings Binding[] resources Resource[] } model Binding { id String @id @default(cuid()) environmentId String environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade) accountId String account Account @relation(fields: [accountId], references: [id], onDelete: Cascade) @@unique([environmentId, accountId]) } model Resource { id String @id @default(cuid()) kind String name String environmentId String environment Environment @relation(fields: [environmentId], references: [id]) accountId String account Account @relation(fields: [accountId], references: [id]) origin String @default("cli") // file | cli | fleet | imported managedBy String @default("manual") // gitops | manual | auto sourceRef String? desiredSpec Json @default("{}") actualSpec Json? platformRef String? status String @default("pending") // pending | creating | ready | updating | deleting | error statusMessage String? lastReconciled DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([kind, name, environmentId]) @@index([environmentId]) @@index([accountId]) @@index([kind, status]) } model Secret { id String @id @default(cuid()) name String @unique // Encrypted data — application-layer encryption as fallback if Infisical unavailable data Json @default("{}") version Int @default(1) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // ── Fleet ── model Fleet { id String @id @default(cuid()) name String environmentId String accountId String selector Json // fact-matching rules onboardPipeline Json // step definitions offboardPipeline Json? approvalConfig Json? status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt members FleetMember[] } model FleetMember { id String @id @default(cuid()) fleetId String fleet Fleet @relation(fields: [fleetId], references: [id], onDelete: Cascade) serverId String status String // discovered | pending | onboarding | active | offboarding | removed joinedAt DateTime @default(now()) @@index([fleetId]) } // ── Git sources (for sync controller) ── model GitSource { id String @id @default(cuid()) name String @unique repo String branch String @default("main") path String @default("environments/") lastSync DateTime? createdAt DateTime @default(now()) } // ── Existing v1.0 models (kept for bastion/agent compatibility) ── 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") lastHeartbeat DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt agent Agent? } 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? facts Json? // hardware facts reported by agent @@index([serverId]) } model JoinToken { id String @id @default(uuid()) token String @unique type String @default("one-time") label String? usedBy String? usedAt DateTime? revokedAt DateTime? createdAt DateTime @default(now()) expiresAt DateTime? } model Bastion { id String @id @default(uuid()) hostname String @unique network String serverIp String status String @default("offline") lastHeartbeat DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Cluster { id String @id @default(uuid()) name String @unique cloud String @default("baremetal") environment String @default("default") kubeconfigEnc String? labels Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }