generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" } // ───────────────────────────────────────────────────────────────────────────── // Enums // ───────────────────────────────────────────────────────────────────────────── enum UserRole { ADMIN USER } enum RequestState { PENDING CHANGES_REQUESTED APPROVED REJECTED EXPIRED CONSUMED CANCELLED } enum NotificationType { REQUEST_CREATED REQUEST_UPDATED REQUEST_APPROVED REQUEST_REJECTED CHANGES_REQUESTED REQUEST_CONSUMED REQUEST_CANCELLED REQUEST_EXPIRED AGENT_DISABLED ADMIN_ACTION SETTINGS_CHANGED } enum GlobalSettingType { registration_enabled requests_enabled } // ───────────────────────────────────────────────────────────────────────────── // Models // ───────────────────────────────────────────────────────────────────────────── /// A global (admin-only) platform setting, keyed by type. model GlobalSetting { type GlobalSettingType @id value String updatedAt DateTime @updatedAt } /// A human account. The first registered user becomes ADMIN. model User { id Int @id @default(autoincrement()) /// Normalized (lowercased) username — enforces case-insensitive uniqueness. username String @unique /// Original-cased display handle shown in the UI. displayName String password String role UserRole @default(USER) // Optional TOTP two-factor auth totpSecret String? totpEnabled Boolean @default(false) // Admin can disable a human account entirely disabled Boolean @default(false) // Auto-delete of old change requests (opt-in, min 7 days, default 30) autoDeleteEnabled Boolean @default(false) autoDeleteDays Int @default(30) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt sessions Session[] agents Agent[] changeRequests ChangeRequest[] @relation("Owner") decisions ChangeRequest[] @relation("Approver") notifications Notification[] auditLogs AuditLog[] @relation("AuditActor") } /// A browser session for a human account (cookie-based auth). model Session { id Int @id @default(autoincrement()) hash String @unique userId Int user User @relation(fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@index([userId]) } /// An AGENT account. Fully managed by a human owner. Holds exactly one API key. model Agent { id Int @id @default(autoincrement()) name String description String? website String? iconUrl String? /// The single API key the agent authenticates with. apiKey String @unique disabled Boolean @default(false) /// Max simultaneous pending requests (1..10, human-configurable, default 5). maxPendingRequests Int @default(5) ownerId Int owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt changeRequests ChangeRequest[] @@index([ownerId]) } /// A structured change request submitted by an agent for human approval. model ChangeRequest { id Int @id @default(autoincrement()) /// Public UUID (v4) exposed to agents as request_id. publicId String @unique @default(uuid()) title String description String? /// Normalized changes array (diffs normalized, canonical ordering). changes Json /// Original changes exactly as submitted (audit trail). rawChanges Json metadata Json? /// SHA-256 hash over the canonical (title + description + normalized changes). contentHash String state RequestState @default(PENDING) expiresAt DateTime // Decision fields comment String? decidedAt DateTime? approverId Int? approver User? @relation("Approver", fields: [approverId], references: [id], onDelete: SetNull) /// HMAC-SHA256 signature of the approval receipt (platform-signed decision). signature String? receiptIssuedAt DateTime? consumedAt DateTime? cancelledAt DateTime? // Update tracking for the CHANGES_REQUESTED → resubmit loop updateCount Int @default(0) /// True after an agent resubmits following a CHANGES_REQUESTED decision. resubmitted Boolean @default(false) lastAgentUpdateAt DateTime? agentId Int agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade) // The human who owns/reviews this request (the agent's owner at creation time). userId Int user User @relation("Owner", fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId, state]) @@index([agentId, state]) @@index([createdAt]) } /// An in-app notification for a human account. model Notification { id Int @id @default(autoincrement()) type NotificationType title String message String /// Optional link to a change request (publicId). requestPublicId String? read Boolean @default(false) userId Int user User @relation(fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@index([userId, read]) @@index([userId, createdAt]) } /// An audit log entry. Records privileged/admin actions and payload views. model AuditLog { id Int @id @default(autoincrement()) action String /// Human-readable detail / JSON string of context. detail String? /// Optional target references. targetType String? targetId String? actorId Int? actor User? @relation("AuditActor", fields: [actorId], references: [id], onDelete: SetNull) createdAt DateTime @default(now()) @@index([createdAt]) @@index([actorId]) }