// DevOS v1 — PostgreSQL + Prisma schema // 设计原则(04-DATABASE-SCHEMA.md): // - resources 表承担统一身份/发现/图谱公共字段; // - 具备大量运行状态的资源使用类型化领域表(servers/websites/...),通过 resourceId 与 resources 一对一关联; // - 资源关系独立成表(resource_relations),图谱可演进而非堆在一张大表; // - 凭据密文独立存储,绝不与普通元数据混放。 generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ============ 枚举 ============ enum ResourceType { SERVER DOMAIN WEBSITE REPOSITORY CERTIFICATE DATABASE CONTAINER EMAIL_ACCOUNT PROJECT TASK NOTE DOCUMENT BACKUP DEPLOYMENT MONITOR CREDENTIAL AUTOMATION AI_RUN } enum ResourceStatus { PENDING ACTIVE ERROR OFFLINE ARCHIVED UNKNOWN } enum WorkspaceRole { OWNER ADMIN MEMBER VIEWER } enum RiskLevel { LOW MEDIUM HIGH CRITICAL } enum ActorType { USER SYSTEM AGENT MCP } enum JobStatus { PENDING RUNNING SUCCEEDED FAILED CANCELLED DEAD } enum DeploymentStatus { QUEUED RUNNING SUCCEEDED FAILED CANCELLED } enum CredentialType { SSH_PASSWORD SSH_KEY API_TOKEN DNS_PROVIDER GIT_TOKEN } enum ServerAuthType { PASSWORD SSH_KEY } enum TriggerType { MANUAL CRON EVENT WEBHOOK } enum AiRunStatus { PLANNING AWAITING_APPROVAL RUNNING SUCCEEDED FAILED REJECTED } // ============ 身份与工作区 ============ /// # 数据库表 users:平台用户 model User { /// # 数据库字段 邮箱(登录名,唯一) id String @id @default(uuid()) email String @unique /// # 数据库字段 密码哈希(argon2id/scrypt,绝不明文) passwordHash String name String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt memberships WorkspaceMember[] @@map("users") } /// # 数据库表 workspaces:租户/安全边界 model Workspace { id String @id @default(uuid()) name String slug String @unique createdAt DateTime @default(now()) updatedAt DateTime @updatedAt members WorkspaceMember[] resources Resource[] @@map("workspaces") } /// # 数据库表 workspace_members:用户-工作区关系与角色 model WorkspaceMember { id String @id @default(uuid()) workspaceId String userId String role WorkspaceRole @default(MEMBER) createdAt DateTime @default(now()) workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([workspaceId, userId]) @@index([userId]) @@map("workspace_members") } // ============ 统一资源模型 ============ /// # 数据库表 resources:统一资源身份/发现/图谱公共字段 model Resource { id String @id @default(uuid()) workspaceId String type ResourceType name String provider String? status ResourceStatus @default(PENDING) /// # 数据库字段 扩展元数据(敏感信息禁止写入) metadata Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) outgoingRelations ResourceRelation[] @relation("source") incomingRelations ResourceRelation[] @relation("target") credential Credential? server Server? container Container? website Website? domain Domain? deployment Deployment? monitor Monitor? automation Automation? aiRun AiRun? gitConnection GitConnection? repository Repository? task Task? note Note? certificate Certificate? @@index([workspaceId]) @@index([type, status]) @@index([workspaceId, type]) @@map("resources") } /// # 数据库表 resource_relations:资源关系图(有向边) model ResourceRelation { id String @id @default(uuid()) workspaceId String sourceId String targetId String /// # 数据库字段 关系类型,如 deployed_on / has_domain / resolves_to / monitors relationType String metadata Json @default("{}") createdAt DateTime @default(now()) source Resource @relation("source", fields: [sourceId], references: [id], onDelete: Cascade) target Resource @relation("target", fields: [targetId], references: [id], onDelete: Cascade) @@unique([sourceId, targetId, relationType]) @@index([sourceId]) @@index([targetId]) @@index([workspaceId]) @@map("resource_relations") } // ============ 凭据保险库 ============ /// # 数据库表 credentials:加密凭据(信封加密后的密文,禁止记录明文) model Credential { id String @id @default(uuid()) workspaceId String resourceId String? @unique name String type CredentialType provider String? /// # 数据库字段 AES-256-GCM 密文 JSON(iv/tag/ciphertext,base64) encryptedData String metadata Json @default("{}") lastUsedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource? @relation(fields: [resourceId], references: [id], onDelete: SetNull) @@index([workspaceId]) @@map("credentials") } // ============ 审计 / 事件 / 任务 ============ /// # 数据库表 audit_logs:审计日志(每个变更必写) model AuditLog { id String @id @default(uuid()) workspaceId String actorType ActorType actorId String? action String resourceType String? resourceId String? riskLevel RiskLevel @default(LOW) status String @default("SUCCESS") requestId String? ip String? metadata Json @default("{}") createdAt DateTime @default(now()) @@index([workspaceId, createdAt]) @@index([resourceType, resourceId]) @@map("audit_logs") } /// # 数据库表 events:领域事件(持久化 + 分发) model EventRecord { id String @id @default(uuid()) workspaceId String eventType String resourceId String? payload Json @default("{}") correlationId String? createdAt DateTime @default(now()) @@index([eventType, createdAt]) @@index([workspaceId, createdAt]) @@map("events") } /// # 数据库表 jobs:异步任务(持久化状态机 + Redis 派发) model Job { id String @id @default(uuid()) workspaceId String type String status JobStatus @default(PENDING) payload Json @default("{}") result Json? error String? attempts Int @default(0) maxAttempts Int @default(3) runAt DateTime @default(now()) startedAt DateTime? finishedAt DateTime? correlationId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([status, runAt]) @@index([workspaceId]) @@map("jobs") } // ============ 基础设施领域表 ============ /// # 数据库表 servers:受管计算目标(SSH) model Server { id String @id @default(uuid()) workspaceId String resourceId String @unique name String host String port Int @default(22) username String authType ServerAuthType @default(SSH_KEY) provider String @default("ssh") credentialId String? osInfo String? labels Json @default("{}") lastSeenAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) containers Container[] websites Website[] monitors Monitor[] @@index([workspaceId]) @@map("servers") } /// # 数据库表 containers:Docker 容器发现结果 model Container { id String @id @default(uuid()) workspaceId String resourceId String @unique serverId String containerId String name String image String state String ports Json @default("{}") labels Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) server Server @relation(fields: [serverId], references: [id], onDelete: Cascade) @@index([serverId]) @@index([workspaceId]) @@map("containers") } /// # 数据库表 websites:可部署应用资源 model Website { id String @id @default(uuid()) workspaceId String resourceId String @unique name String primaryDomain String? serverId String? repositoryUrl String? /// # 数据库字段 关联仓库(Git Provider 凭据克隆;优先于 repositoryUrl) repositoryId String? /// # 数据库字段 部署环境:development / staging / production environment String @default("production") /// # 数据库字段 部署类型:DOCKER / STATIC / NODE deployType String @default("DOCKER") /// # 数据库字段 容器对外端口 port Int @default(80) healthPath String @default("/") env Json @default("{}") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) server Server? @relation(fields: [serverId], references: [id], onDelete: SetNull) deployments Deployment[] monitors Monitor[] repository Repository? @relation(fields: [repositoryId], references: [id]) @@index([workspaceId]) @@map("websites") } /// # 数据库表 domains:域名与 DNS 提供商绑定 model Domain { id String @id @default(uuid()) workspaceId String resourceId String @unique name String provider String @default("MANUAL") credentialId String? expiresAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) records DomainRecord[] @@index([workspaceId]) @@map("domains") } /// # 数据库表 domain_records:域名记录(手动 provider 时为本地事实源) model DomainRecord { id String @id @default(uuid()) workspaceId String domainId String type String name String value String ttl Int @default(300) proxied Boolean @default(false) providerRecordId String? updatedAt DateTime @updatedAt createdAt DateTime @default(now()) domain Domain @relation(fields: [domainId], references: [id], onDelete: Cascade) @@unique([domainId, type, name, value]) @@index([workspaceId]) @@map("domain_records") } /// # 数据库表 deployments:部署执行记录 model Deployment { id String @id @default(uuid()) workspaceId String resourceId String @unique websiteId String status DeploymentStatus @default(QUEUED) trigger String @default("MANUAL") progress Int @default(0) /// # 数据库字段 失败原因(截断存储,不含敏感信息) error String? commit String? /// # 数据库字段 部署环境:development / staging / production environment String? /// # 数据库字段 构建策略:DOCKERFILE / STATIC_COPY buildStrategy String? /// # 数据库字段 部署策略:DOCKER_CONTAINER / STATIC_DIR deployStrategy String? /// # 数据库字段 健康验证证据 {ok,status,latencyMs,checkedAt} verification Json? /// # 数据库字段 部署参数快照(分支、镜像 tag 等) config Json @default("{}") createdAt DateTime @default(now()) finishedAt DateTime? resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) website Website @relation(fields: [websiteId], references: [id], onDelete: Cascade) logs DeploymentLog[] @@index([websiteId, createdAt]) @@index([workspaceId, createdAt]) @@map("deployments") } /// # 数据库表 deployment_logs:部署日志行(顺序) model DeploymentLog { id String @id @default(uuid()) deploymentId String seq Int stream String @default("stdout") line String createdAt DateTime @default(now()) deployment Deployment @relation(fields: [deploymentId], references: [id], onDelete: Cascade) @@index([deploymentId, seq]) @@map("deployment_logs") } /// # 数据库表 monitors:健康检查配置 model Monitor { id String @id @default(uuid()) workspaceId String resourceId String @unique websiteId String? serverId String? name String /// # 数据库字段 检查类型:HTTP / TCP kind String @default("HTTP") target String intervalSec Int @default(60) timeoutMs Int @default(10000) enabled Boolean @default(true) lastStatus String @default("UNKNOWN") consecutiveFailures Int @default(0) lastCheckedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) website Website? @relation(fields: [websiteId], references: [id], onDelete: Cascade) server Server? @relation(fields: [serverId], references: [id], onDelete: Cascade) results MonitorResult[] @@index([enabled, lastCheckedAt]) @@index([workspaceId]) @@map("monitors") } /// # 数据库表 monitor_results:监控结果历史 model MonitorResult { id String @id @default(uuid()) monitorId String ok Boolean status String? latencyMs Int? error String? createdAt DateTime @default(now()) monitor Monitor @relation(fields: [monitorId], references: [id], onDelete: Cascade) @@index([monitorId, createdAt]) @@map("monitor_results") } // ============ 自动化 ============ /// # 数据库表 automations:Trigger → Condition → Action 工作流 model Automation { id String @id @default(uuid()) workspaceId String resourceId String @unique name String enabled Boolean @default(false) triggerType TriggerType /// # 数据库字段 触发器配置(cron 表达式 / 事件名 / webhook token) triggerConfig Json @default("{}") /// # 数据库字段 条件列表 [{path,op,value}] conditions Json @default("[]") /// # 数据库字段 动作列表 [{type,args,timeoutMs,retries}] actions Json @default("[]") lastTriggeredAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) runs AutomationRun[] @@index([workspaceId]) @@index([triggerType, enabled]) @@map("automations") } /// # 数据库表 automation_runs:自动化执行历史 model AutomationRun { id String @id @default(uuid()) automationId String workspaceId String status String @default("RUNNING") trigger Json @default("{}") stepIndex Int @default(0) logs Json @default("[]") error String? correlationId String? startedAt DateTime @default(now()) finishedAt DateTime? automation Automation @relation(fields: [automationId], references: [id], onDelete: Cascade) @@index([automationId, startedAt]) @@map("automation_runs") } // ============ AI Gateway ============ /// # 数据库表 ai_runs:AI 运行(计划→风控→审批→执行→验证) model AiRun { id String @id @default(uuid()) workspaceId String resourceId String @unique objective String status AiRunStatus @default(PLANNING) /// # 数据库字段 来源:UI / CHAT / MCP / AUTOMATION source String @default("UI") riskLevel RiskLevel @default(LOW) /// # 数据库字段 计划步骤 [{tool,args,reason,risk}] plan Json @default("[]") /// # 数据库字段 执行历史 [{tool,args,result,ok}] toolCalls Json @default("[]") /// # 数据库字段 验证结果 {ok, checks[]} verification Json? result Json? error String? approvedBy String? approvedAt DateTime? correlationId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) @@index([workspaceId, createdAt]) @@map("ai_runs") } // ============ Phase 2:Git / 生产力 / 通知 / 证书 ============ /// # 数据库表 git_connections:Git Provider 连接(GitHub/GitLab/Gitea + 凭据引用) model GitConnection { id String @id @default(uuid()) workspaceId String resourceId String @unique name String /// # 数据库字段 提供商:GITHUB / GITLAB / GITEA provider String /// # 数据库字段 自建实例 API 基址(Gitea/GitLab 自托管时填写) apiBase String? credentialId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) repositories Repository[] @@index([workspaceId]) } /// # 数据库表 repositories:外部代码仓库聚合(不实现 Git 托管) model Repository { id String @id @default(uuid()) workspaceId String resourceId String @unique connectionId String? provider String @default("GENERIC") /// # 数据库字段 全名 owner/repo fullName String /// # 数据库字段 HTTPS 克隆地址(不含凭据) cloneUrl String defaultBranch String @default("main") /// # 数据库字段 private 标记 visibility String @default("private") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) connection GitConnection? @relation(fields: [connectionId], references: [id], onDelete: SetNull) websites Website[] @@unique([workspaceId, cloneUrl]) @@index([workspaceId]) } /// # 数据库表 tasks:工作项(backlog/todo/doing/review/done) model Task { id String @id @default(uuid()) workspaceId String resourceId String @unique title String description String @default("") /// # 数据库字段 状态:BACKLOG / TODO / DOING / REVIEW / DONE status String @default("TODO") /// # 数据库字段 优先级:LOW / MEDIUM / HIGH / URGENT priority String @default("MEDIUM") labels Json @default("[]") dueAt DateTime? /// # 数据库字段 关联项目资源 ID(可空) projectId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) @@index([workspaceId, status]) @@index([projectId]) } /// # 数据库表 notes:笔记/知识(markdown 存储) model Note { id String @id @default(uuid()) workspaceId String resourceId String @unique title String /// # 数据库字段 正文(HTML,TipTap 富文本) content String @default("") tags Json @default("[]") projectId String? /// # 数据库字段 置顶 pinned Boolean @default(false) /// # 数据库字段 文件夹(分组) folder String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) @@index([workspaceId]) } /// # 数据库表 notifications:站内通知(自动化/事件投递) model Notification { id String @id @default(uuid()) workspaceId String resourceId String? title String body String @default("") /// # 数据库字段 级别:INFO / WARN / ERROR level String @default("INFO") readAt DateTime? createdAt DateTime @default(now()) @@index([workspaceId, createdAt]) } /// # 数据库表 certificates:TLS 证书统一模型(探针检测) model Certificate { id String @id @default(uuid()) workspaceId String resourceId String @unique /// # 数据库字段 证书主域名 domain String domainId String? /// # 数据库字段 状态:PENDING / ACTIVE / EXPIRED / ERROR status String @default("PENDING") issuer String? notAfter DateTime? lastCheckedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt resource Resource @relation(fields: [resourceId], references: [id], onDelete: Cascade) @@index([workspaceId]) } // ============ v2 Action Runtime ============ /// # 数据库表 devos_action_executions:v2 Action 执行记录(docs 03 §31) model ActionExecution { id String @id actionId String actorType String actorId String? resourceType String? resourceId String? status String input Json? output Json? error Json? requestId String? rootExecutionId String? parentExecutionId String? durationMs Int? startedAt DateTime? completedAt DateTime? createdAt DateTime @default(now()) @@index([actionId, createdAt]) @@index([status]) }